6083 lines
209 KiB
C++
6083 lines
209 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MovieSceneToolHelpers.h"
|
|
#include "ActorForWorldTransforms.h"
|
|
#include "Animation/AnimationSettings.h"
|
|
#include "MovieSceneToolsModule.h"
|
|
#include "MovieScene.h"
|
|
#include "Layout/Margin.h"
|
|
#include "Misc/App.h"
|
|
#include "Misc/Paths.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "AssetRegistry/AssetData.h"
|
|
#include "Containers/ArrayView.h"
|
|
#include "ISequencer.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Widgets/Input/SComboBox.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Compilation/MovieSceneCompiledDataManager.h"
|
|
#include "EditorDirectories.h"
|
|
#include "Sections/MovieSceneDoubleSection.h"
|
|
#include "Tracks/MovieSceneDoubleTrack.h"
|
|
#include "Sections/MovieSceneFloatSection.h"
|
|
#include "Tracks/MovieSceneFloatTrack.h"
|
|
#include "Tracks/MovieSceneCameraCutTrack.h"
|
|
#include "Sections/MovieScene3DTransformSection.h"
|
|
#include "Sections/MovieScene3DConstraintSection.h"
|
|
#include "Tracks/MovieScene3DTransformTrack.h"
|
|
#include "Sections/MovieSceneCinematicShotSection.h"
|
|
#include "LevelSequence.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "DesktopPlatformModule.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "MovieSceneTranslatorEDL.h"
|
|
#include "MessageLogModule.h"
|
|
#include "IMessageLogListing.h"
|
|
#include "FbxImporter.h"
|
|
#include "MovieSceneToolsProjectSettings.h"
|
|
#include "MovieSceneToolsUserSettings.h"
|
|
#include "CineCameraActor.h"
|
|
#include "CineCameraComponent.h"
|
|
#include "Math/UnitConversion.h"
|
|
#include "PropertyEditorModule.h"
|
|
#include "IDetailsView.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Editor.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "LevelEditorViewport.h"
|
|
#include "AssetToolsModule.h"
|
|
#include "Channels/MovieSceneChannelProxy.h"
|
|
#include "Evaluation/MovieSceneEvaluationTrack.h"
|
|
#include "Evaluation/MovieSceneEvaluationTemplateInstance.h"
|
|
#include "IMovieScenePlayer.h"
|
|
#include "Tracks/MovieSceneCinematicShotTrack.h"
|
|
#include "Tracks/MovieSceneCameraCutTrack.h"
|
|
#include "Tracks/MovieScene3DConstraintTrack.h"
|
|
#include "Sections/MovieSceneCameraCutSection.h"
|
|
#include "Engine/LevelStreaming.h"
|
|
#include "FbxExporter.h"
|
|
#include "Serialization/ObjectWriter.h"
|
|
#include "Serialization/ObjectReader.h"
|
|
#include "AnimationRecorder.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "ILiveLinkClient.h"
|
|
#include "LiveLinkPresetTypes.h"
|
|
#include "LiveLinkSourceSettings.h"
|
|
#include "Features/IModularFeatures.h"
|
|
#include "Tracks/MovieSceneSpawnTrack.h"
|
|
#include "Sections/MovieSceneSpawnSection.h"
|
|
#include "Tracks/MovieSceneSubTrack.h"
|
|
#include "Sections/MovieSceneSubSection.h"
|
|
#include "Exporters/AnimSeqExportOption.h"
|
|
#include "Widgets/Input/NumericTypeInterface.h"
|
|
#include "FrameNumberDetailsCustomization.h"
|
|
#include "PropertyEditorDelegates.h"
|
|
#include "INodeAndChannelMappings.h"
|
|
#include "BakingAnimationKeySettings.h"
|
|
#include "Channels/MovieSceneChannelTraits.h"
|
|
#include "Channels/MovieSceneIntegerChannel.h"
|
|
#include "Channels/MovieSceneByteChannel.h"
|
|
#include "Channels/MovieSceneFloatChannel.h"
|
|
#include "Channels/MovieSceneDoubleChannel.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "EntitySystem/Interrogation/MovieSceneInterrogationLinker.h"
|
|
#include "ConstraintsManager.h"
|
|
#include "IMovieScenePlaybackClient.h"
|
|
#include "SequencerChannelInterface.h"
|
|
#include "Constraints/MovieSceneConstraintChannelHelper.inl"
|
|
#include "Exporters/FbxExportOption.h"
|
|
#include "Bindings/MovieSceneSpawnableBinding.h"
|
|
#include "Tracks/MovieSceneBindingLifetimeTrack.h"
|
|
#include "Sections/MovieSceneBindingLifetimeSection.h"
|
|
#include "GameFramework/Character.h"
|
|
#include "Systems/MovieSceneTransformOriginSystem.h"
|
|
#include "Tracks/IMovieSceneTransformOrigin.h"
|
|
|
|
/* FSkelMeshRecorder
|
|
***********/
|
|
|
|
void FSkelMeshRecorderState::Init(USkeletalMeshComponent* InComponent)
|
|
{
|
|
SkelComp = InComponent;
|
|
|
|
if (InComponent)
|
|
{
|
|
CachedSkelCompForcedLodModel = InComponent->GetForcedLOD();
|
|
InComponent->SetForcedLOD(1);
|
|
|
|
// turn off URO and make sure we always update even if out of view
|
|
bCachedEnableUpdateRateOptimizations = InComponent->bEnableUpdateRateOptimizations;
|
|
CachedVisibilityBasedAnimTickOption = InComponent->VisibilityBasedAnimTickOption;
|
|
|
|
InComponent->bEnableUpdateRateOptimizations = false;
|
|
InComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
|
|
}
|
|
}
|
|
|
|
void FSkelMeshRecorderState::FinishRecording()
|
|
{
|
|
if (SkelComp.IsValid())
|
|
{
|
|
// restore force lod setting
|
|
SkelComp->SetForcedLOD(CachedSkelCompForcedLodModel);
|
|
|
|
// restore update flags
|
|
SkelComp->bEnableUpdateRateOptimizations = bCachedEnableUpdateRateOptimizations;
|
|
SkelComp->VisibilityBasedAnimTickOption = CachedVisibilityBasedAnimTickOption;
|
|
}
|
|
}
|
|
|
|
|
|
/* MovieSceneToolHelpers
|
|
*****************************************************************************/
|
|
|
|
void MovieSceneToolHelpers::TrimSection(const TSet<UMovieSceneSection*>& Sections, FQualifiedFrameTime Time, bool bTrimLeft, bool bDeleteKeys)
|
|
{
|
|
for (UMovieSceneSection* Section : Sections)
|
|
{
|
|
if (Section)
|
|
{
|
|
Section->TrimSection(Time, bTrimLeft, bDeleteKeys);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::CanTrimSectionLeft(const TSet<UMovieSceneSection*>& Sections, FQualifiedFrameTime Time)
|
|
{
|
|
for (UMovieSceneSection* Section : Sections)
|
|
{
|
|
if (Section)
|
|
{
|
|
TRange<FFrameNumber> Range = Section->GetRange();
|
|
TArray<TRange<FFrameNumber> > Splits = Range.Split(Time.Time.FrameNumber);
|
|
|
|
if (Splits.Num() > 0)
|
|
{
|
|
if (Splits[0] == Range) // can't split
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
TRange<FFrameNumber> Head = Splits[0];
|
|
|
|
if (!Head.IsEmpty())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::CanTrimSectionRight(const TSet<UMovieSceneSection*>& Sections, FQualifiedFrameTime Time)
|
|
{
|
|
for (UMovieSceneSection* Section : Sections)
|
|
{
|
|
if (Section)
|
|
{
|
|
TRange<FFrameNumber> Range = Section->GetRange();
|
|
TArray<TRange<FFrameNumber> > Splits = Range.Split(Time.Time.FrameNumber);
|
|
|
|
if (Splits.Num() > 0)
|
|
{
|
|
if (Splits[0] == Range) // can't split
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
TRange<FFrameNumber> Tail = Splits.Last();
|
|
|
|
if (!Tail.IsEmpty())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MovieSceneToolHelpers::TrimOrExtendSection(UMovieSceneTrack* Track, TOptional<int32> SpecifiedRowIndex, FQualifiedFrameTime Time, bool bTrimOrExtendLeft, bool bDeleteKeys)
|
|
{
|
|
Track->Modify();
|
|
|
|
int32 StartRowIndex = SpecifiedRowIndex.IsSet() ? SpecifiedRowIndex.GetValue() : 0;
|
|
int32 EndRowIndex = SpecifiedRowIndex.IsSet() ? SpecifiedRowIndex.GetValue() : Track->GetMaxRowIndex();
|
|
|
|
for (int32 RowIndex = StartRowIndex; RowIndex <= EndRowIndex; ++RowIndex)
|
|
{
|
|
// First, trim all intersecting sections
|
|
bool bAnyIntersects = false;
|
|
for (UMovieSceneSection* Section : Track->GetAllSections())
|
|
{
|
|
if (Section->GetRowIndex() == RowIndex && Section->HasStartFrame() && Section->HasEndFrame() && Section->GetRange().Contains(Time.Time.GetFrame()))
|
|
{
|
|
Section->TrimSection(Time, bTrimOrExtendLeft, bDeleteKeys);
|
|
bAnyIntersects = true;
|
|
}
|
|
}
|
|
|
|
// If there aren't any intersects, extend the closest start/end
|
|
if (!bAnyIntersects)
|
|
{
|
|
UMovieSceneSection* ClosestSection = nullptr;
|
|
TOptional<FFrameNumber> MinDiff;
|
|
|
|
for (UMovieSceneSection* Section : Track->GetAllSections())
|
|
{
|
|
if (Section->GetRowIndex() == RowIndex)
|
|
{
|
|
if (bTrimOrExtendLeft)
|
|
{
|
|
if (Section->HasStartFrame())
|
|
{
|
|
FFrameNumber StartFrame = Section->GetInclusiveStartFrame();
|
|
if (StartFrame > Time.Time.GetFrame())
|
|
{
|
|
FFrameNumber Diff = StartFrame - Time.Time.GetFrame();
|
|
if (!MinDiff.IsSet() || Diff < MinDiff.GetValue())
|
|
{
|
|
ClosestSection = Section;
|
|
MinDiff = Diff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Section->HasEndFrame())
|
|
{
|
|
FFrameNumber EndFrame = Section->GetExclusiveEndFrame();
|
|
if (EndFrame < Time.Time.GetFrame())
|
|
{
|
|
FFrameNumber Diff = Time.Time.GetFrame() - EndFrame;
|
|
if (!MinDiff.IsSet() || Diff < MinDiff.GetValue())
|
|
{
|
|
ClosestSection = Section;
|
|
MinDiff = Diff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ClosestSection)
|
|
{
|
|
ClosestSection->Modify();
|
|
if (bTrimOrExtendLeft)
|
|
{
|
|
ClosestSection->SetStartFrame(Time.Time.GetFrame());
|
|
}
|
|
else
|
|
{
|
|
ClosestSection->SetEndFrame(Time.Time.GetFrame());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void MovieSceneToolHelpers::SplitSection(const TSet<UMovieSceneSection*>& Sections, FQualifiedFrameTime Time, bool bDeleteKeys)
|
|
{
|
|
for (UMovieSceneSection* Section : Sections)
|
|
{
|
|
if (Section)
|
|
{
|
|
Section->SplitSection(Time, bDeleteKeys);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::CanSplitSection(const TSet<UMovieSceneSection*>& Sections, FQualifiedFrameTime Time)
|
|
{
|
|
for (UMovieSceneSection* Section : Sections)
|
|
{
|
|
if (Section)
|
|
{
|
|
TRange<FFrameNumber> Range = Section->GetRange();
|
|
TArray<TRange<FFrameNumber> > Splits = Range.Split(Time.Time.FrameNumber);
|
|
|
|
if (Splits.Num() > 0)
|
|
{
|
|
if (Splits[0] == Range) // can't split
|
|
{
|
|
continue;
|
|
}
|
|
else if (Splits.Num() == 2)
|
|
{
|
|
TRange<FFrameNumber> Head = Splits[0];
|
|
TRange<FFrameNumber> Tail = Splits[1];
|
|
|
|
if (!Head.IsEmpty() && !Tail.IsEmpty())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FTransform MovieSceneToolHelpers::GetTransformOriginForFocusedSequence(TSharedPtr<ISequencer> InSequencer)
|
|
{
|
|
FTransform TransformOrigin;
|
|
|
|
const IMovieScenePlaybackClient* Client = InSequencer->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 TArray<FMovieSceneSequenceID>& Hierarchy = InSequencer->GetSubSequenceHierarchy();
|
|
|
|
const FMovieSceneRootEvaluationTemplateInstance& EvaluationTemplate = InSequencer->GetEvaluationTemplate();
|
|
const UMovieSceneEntitySystemLinker* EntityLinker = EvaluationTemplate.GetEntitySystemLinker();
|
|
if(!EntityLinker)
|
|
{
|
|
return TransformOrigin;
|
|
}
|
|
|
|
const UMovieSceneTransformOriginSystem* TransformOriginSystem = EntityLinker->FindSystem<UMovieSceneTransformOriginSystem>();
|
|
|
|
if(!TransformOriginSystem)
|
|
{
|
|
return TransformOrigin;
|
|
}
|
|
|
|
const TMap<FMovieSceneSequenceID, UE::MovieScene::FInstanceHandle> SequenceIDToInstanceHandle = TransformOriginSystem->GetSequenceIDToInstanceHandle();
|
|
|
|
// Transform Origins will be pre-multiplied at this step, so only retrieve the entry for the currently focused sub-sequence.
|
|
if(Hierarchy.Num())
|
|
{
|
|
const FMovieSceneSequenceID CurrentSequence = Hierarchy.Last();
|
|
if(SequenceIDToInstanceHandle.Contains(CurrentSequence))
|
|
{
|
|
const UE::MovieScene::FInstanceHandle CurrentHandle = SequenceIDToInstanceHandle[CurrentSequence];
|
|
// Overrides the origin only if entry is present
|
|
TransformOriginSystem->GetTransformOrigin(CurrentHandle, TransformOrigin);
|
|
}
|
|
}
|
|
|
|
return TransformOrigin;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ParseShotName(const FString& InShotName, FString& ShotPrefix, uint32& ShotNumber, uint32& TakeNumber, uint32& ShotNumberDigits, uint32& TakeNumberDigits)
|
|
{
|
|
// Parse a shot name
|
|
//
|
|
// sht010:
|
|
// ShotPrefix = sht
|
|
// ShotNumber = 10
|
|
// TakeNumber = 1 (default)
|
|
//
|
|
// sp020_002
|
|
// ShotPrefix = sp
|
|
// ShotNumber = 20
|
|
// TakeNumber = 2
|
|
//
|
|
|
|
// If the shot prefix is known and is already at the beginning of the shot name, remove it and parse the rest
|
|
//
|
|
// sq050_sh0010_01
|
|
// ShotPrefix = sq050_sh, ie. parse(0010_01)
|
|
// ShotNumber = 10
|
|
// TakeNumber = 1
|
|
FString ShotName = InShotName;
|
|
ShotName.RemoveFromStart(ShotPrefix);
|
|
|
|
const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault<UMovieSceneToolsProjectSettings>();
|
|
|
|
uint32 FirstShotNumberIndex = INDEX_NONE;
|
|
uint32 LastShotNumberIndex = INDEX_NONE;
|
|
bool bInShotNumber = false;
|
|
|
|
uint32 FirstTakeNumberIndex = INDEX_NONE;
|
|
uint32 LastTakeNumberIndex = INDEX_NONE;
|
|
bool bInTakeNumber = false;
|
|
|
|
bool bFoundTakeSeparator = false;
|
|
TOptional<uint32> ParsedTakeNumber;
|
|
TakeNumber = ProjectSettings->FirstTakeNumber;
|
|
|
|
for (int32 CharIndex = 0; CharIndex < ShotName.Len(); ++CharIndex)
|
|
{
|
|
if (FChar::IsDigit(ShotName[CharIndex]))
|
|
{
|
|
// Find shot number indices
|
|
if (FirstShotNumberIndex == INDEX_NONE)
|
|
{
|
|
bInShotNumber = true;
|
|
FirstShotNumberIndex = CharIndex;
|
|
}
|
|
if (bInShotNumber)
|
|
{
|
|
LastShotNumberIndex = CharIndex;
|
|
}
|
|
|
|
if (FirstShotNumberIndex != INDEX_NONE && LastShotNumberIndex != INDEX_NONE)
|
|
{
|
|
if (bFoundTakeSeparator)
|
|
{
|
|
// Find take number indices
|
|
if (FirstTakeNumberIndex == INDEX_NONE)
|
|
{
|
|
bInTakeNumber = true;
|
|
FirstTakeNumberIndex = CharIndex;
|
|
}
|
|
if (bInTakeNumber)
|
|
{
|
|
LastTakeNumberIndex = CharIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FirstShotNumberIndex != INDEX_NONE && LastShotNumberIndex != INDEX_NONE)
|
|
{
|
|
if (ShotName[CharIndex] == ProjectSettings->TakeSeparator[0])
|
|
{
|
|
bFoundTakeSeparator = true;
|
|
ShotNumberDigits = LastShotNumberIndex - FirstShotNumberIndex + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FirstShotNumberIndex != INDEX_NONE)
|
|
{
|
|
if (FirstShotNumberIndex != 0)
|
|
{
|
|
ShotPrefix = ShotName.Left(FirstShotNumberIndex);
|
|
}
|
|
|
|
ShotNumber = FCString::Atoi(*ShotName.Mid(FirstShotNumberIndex, LastShotNumberIndex-FirstShotNumberIndex+1));
|
|
}
|
|
|
|
if (FirstTakeNumberIndex != INDEX_NONE)
|
|
{
|
|
FString TakeStr = ShotName.Mid(FirstTakeNumberIndex, LastTakeNumberIndex-FirstTakeNumberIndex+1);
|
|
if (TakeStr.IsNumeric())
|
|
{
|
|
ParsedTakeNumber = FCString::Atoi(*TakeStr);
|
|
TakeNumberDigits = TakeStr.Len();
|
|
}
|
|
}
|
|
|
|
// If take number wasn't found, start over with the original shot name, search backwards to find the first take separator and assume [shot prefix]_[take number]
|
|
//
|
|
if (!ParsedTakeNumber.IsSet())
|
|
{
|
|
ShotName = InShotName;
|
|
int32 LastSlashPos = ShotName.Find(ProjectSettings->TakeSeparator, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
|
|
if (LastSlashPos != INDEX_NONE)
|
|
{
|
|
if (LastSlashPos != 0)
|
|
{
|
|
ShotPrefix = ShotName.Left(LastSlashPos);
|
|
}
|
|
|
|
FString TakeStr = ShotName.RightChop(LastSlashPos + ProjectSettings->TakeSeparator.Len());
|
|
if (TakeStr.IsNumeric())
|
|
{
|
|
ShotNumber = INDEX_NONE; // Nullify the shot number since we only have a shot prefix
|
|
TakeNumber = FCString::Atoi(*TakeStr);
|
|
TakeNumberDigits = ShotName.Len() - (LastSlashPos + ProjectSettings->TakeSeparator.Len());
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ParsedTakeNumber.IsSet())
|
|
{
|
|
TakeNumber = ParsedTakeNumber.GetValue();
|
|
}
|
|
|
|
if (FirstShotNumberIndex == INDEX_NONE)
|
|
{
|
|
ShotPrefix = InShotName;
|
|
ShotNumber = INDEX_NONE; // Nullify the shot number since we only have a shot prefix
|
|
TakeNumber = 0;
|
|
TakeNumberDigits = ProjectSettings->TakeNumDigits;
|
|
return true;
|
|
}
|
|
|
|
return FirstShotNumberIndex != INDEX_NONE;
|
|
}
|
|
|
|
FString MovieSceneToolHelpers::ComposeShotName(const FString& ShotPrefix, uint32 ShotNumber, uint32 TakeNumber, uint32 ShotNumberDigits, uint32 TakeNumberDigits)
|
|
{
|
|
const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault<UMovieSceneToolsProjectSettings>();
|
|
|
|
FString ShotName = ShotPrefix;
|
|
|
|
if (ShotNumber != INDEX_NONE)
|
|
{
|
|
ShotName += FString::Printf(TEXT("%0*d"), ShotNumberDigits, ShotNumber);
|
|
}
|
|
|
|
if (TakeNumber != INDEX_NONE)
|
|
{
|
|
FString TakeFormat = TEXT("%0") + FString::Printf(TEXT("%d"), TakeNumberDigits) + TEXT("d");
|
|
|
|
ShotName += ProjectSettings->TakeSeparator;
|
|
ShotName += FString::Printf(TEXT("%0*d"), TakeNumberDigits, TakeNumber);
|
|
}
|
|
return ShotName;
|
|
}
|
|
|
|
bool IsPackageNameUnique(const TArray<FAssetData>& ObjectList, FString& NewPackageName)
|
|
{
|
|
for (auto AssetObject : ObjectList)
|
|
{
|
|
if (AssetObject.PackageName.ToString() == NewPackageName)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FString MovieSceneToolHelpers::GenerateNewShotPath(UMovieScene* SequenceMovieScene, FString& NewShotName)
|
|
{
|
|
const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault<UMovieSceneToolsProjectSettings>();
|
|
return GenerateNewSubsequencePath(SequenceMovieScene, ProjectSettings->ShotDirectory, NewShotName);
|
|
}
|
|
|
|
FString MovieSceneToolHelpers::GenerateNewSubsequencePath(UMovieScene * SequenceMovieScene, const FString& SubsequenceDirectory, FString &NewShotName)
|
|
{
|
|
const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault<UMovieSceneToolsProjectSettings>();
|
|
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
|
|
TArray<FAssetData> ObjectList;
|
|
AssetRegistryModule.Get().GetAssetsByClass(ULevelSequence::StaticClass()->GetClassPathName(), ObjectList);
|
|
|
|
UObject* SequenceAsset = SequenceMovieScene->GetOuter();
|
|
UPackage* SequencePackage = SequenceAsset->GetOutermost();
|
|
FString SequencePackageName = SequencePackage->GetName(); // ie. /Game/cine/max/root
|
|
int32 LastSlashPos = SequencePackageName.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);
|
|
FString SequencePath = SequencePackageName.Left(LastSlashPos);
|
|
|
|
FString NewShotPrefix;
|
|
uint32 NewShotNumber = INDEX_NONE;
|
|
uint32 NewTakeNumber = INDEX_NONE;
|
|
uint32 ShotNumberDigits = ProjectSettings->ShotNumDigits;
|
|
uint32 TakeNumberDigits = ProjectSettings->TakeNumDigits;
|
|
ParseShotName(NewShotName, NewShotPrefix, NewShotNumber, NewTakeNumber, ShotNumberDigits, TakeNumberDigits);
|
|
|
|
FString NewDirectory = ComposeShotName(NewShotPrefix, NewShotNumber, INDEX_NONE, ShotNumberDigits, TakeNumberDigits);
|
|
FString NewPath = SequencePath;
|
|
|
|
FString Directory = SubsequenceDirectory;
|
|
if (!Directory.IsEmpty())
|
|
{
|
|
NewPath /= Directory;
|
|
}
|
|
NewPath /= NewDirectory; // put this in the shot directory, ie. /Game/cine/max/shots/shot0010
|
|
|
|
// Make sure this shot path is unique
|
|
FString NewPackageName = NewPath;
|
|
NewPackageName /= NewShotName; // ie. /Game/cine/max/shots/shot0010/shot0010_001
|
|
if (!IsPackageNameUnique(ObjectList, NewPackageName))
|
|
{
|
|
while (1)
|
|
{
|
|
NewShotNumber += ProjectSettings->ShotIncrement;
|
|
NewShotName = ComposeShotName(NewShotPrefix, NewShotNumber, NewTakeNumber, ShotNumberDigits, TakeNumberDigits);
|
|
NewDirectory = ComposeShotName(NewShotPrefix, NewShotNumber, INDEX_NONE, ShotNumberDigits, TakeNumberDigits);
|
|
NewPath = SequencePath;
|
|
if (!Directory.IsEmpty())
|
|
{
|
|
NewPath /= Directory;
|
|
}
|
|
NewPath /= NewDirectory;
|
|
|
|
NewPackageName = NewPath;
|
|
NewPackageName /= NewShotName;
|
|
if (IsPackageNameUnique(ObjectList, NewPackageName))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NewPath;
|
|
}
|
|
|
|
FString MovieSceneToolHelpers::GenerateNewShotName(const TArray<UMovieSceneSection*>& AllSections, FFrameNumber Time)
|
|
{
|
|
const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault<UMovieSceneToolsProjectSettings>();
|
|
|
|
return GenerateNewSubsequenceName(AllSections, ProjectSettings->ShotPrefix, Time);
|
|
}
|
|
|
|
FString MovieSceneToolHelpers::GenerateNewSubsequenceName(const TArray<UMovieSceneSection*>&AllSections, const FString& SubsequencePrefix, FFrameNumber Time)
|
|
{
|
|
const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault<UMovieSceneToolsProjectSettings>();
|
|
|
|
UMovieSceneSubSection* CurrentSection = Cast<UMovieSceneSubSection>(MovieSceneHelpers::FindSectionAtTime(AllSections, Time));
|
|
UMovieSceneSubSection* NextSection = Cast<UMovieSceneSubSection>(MovieSceneHelpers::FindNextSection(AllSections, Time));
|
|
|
|
if (!CurrentSection)
|
|
{
|
|
CurrentSection = Cast<UMovieSceneSubSection>(MovieSceneHelpers::FindPreviousSection(AllSections, Time));
|
|
}
|
|
|
|
if (!NextSection)
|
|
{
|
|
NextSection = CurrentSection;
|
|
}
|
|
|
|
FString NextSectionName = NextSection && NextSection->GetSequence() ? NextSection->GetSequence()->GetName() : FString();
|
|
FString CurrentSectionName = CurrentSection && CurrentSection->GetSequence() ? CurrentSection->GetSequence()->GetName() : FString();
|
|
|
|
// This is the first or last shot
|
|
if ((CurrentSection == nullptr && NextSection) || (CurrentSection != nullptr && CurrentSection == NextSection))
|
|
{
|
|
FString NextShotPrefix = SubsequencePrefix;
|
|
uint32 NextShotNumber = ProjectSettings->FirstShotNumber;
|
|
uint32 NextTakeNumber = ProjectSettings->FirstTakeNumber;
|
|
uint32 ShotNumberDigits = ProjectSettings->ShotNumDigits;
|
|
uint32 TakeNumberDigits = ProjectSettings->TakeNumDigits;
|
|
|
|
if (ParseShotName(NextSectionName, NextShotPrefix, NextShotNumber, NextTakeNumber, ShotNumberDigits, TakeNumberDigits))
|
|
{
|
|
// Valid shot number
|
|
if (NextShotNumber != INDEX_NONE)
|
|
{
|
|
uint32 NewShotNumber = NextShotNumber + ProjectSettings->ShotIncrement;
|
|
return ComposeShotName(NextShotPrefix, NewShotNumber, ProjectSettings->FirstTakeNumber, ShotNumberDigits, TakeNumberDigits);
|
|
}
|
|
// No shot number, but valid take number
|
|
else if (NextTakeNumber != INDEX_NONE)
|
|
{
|
|
uint32 NewTakeNumber = NextTakeNumber + ProjectSettings->ShotIncrement;
|
|
return ComposeShotName(NextShotPrefix, INDEX_NONE, NewTakeNumber, ShotNumberDigits, TakeNumberDigits);
|
|
}
|
|
}
|
|
}
|
|
// This is in between two shots
|
|
else if (CurrentSection && NextSection)
|
|
{
|
|
FString CurrentShotPrefix = SubsequencePrefix;
|
|
uint32 CurrentShotNumber = ProjectSettings->FirstShotNumber;
|
|
uint32 CurrentTakeNumber = ProjectSettings->FirstTakeNumber;
|
|
uint32 CurrentShotNumberDigits = ProjectSettings->ShotNumDigits;
|
|
uint32 CurrentTakeNumberDigits = ProjectSettings->TakeNumDigits;
|
|
|
|
FString NextShotPrefix = SubsequencePrefix;
|
|
uint32 NextShotNumber = ProjectSettings->FirstShotNumber;
|
|
uint32 NextTakeNumber = ProjectSettings->FirstTakeNumber;
|
|
uint32 NextShotNumberDigits = ProjectSettings->ShotNumDigits;
|
|
uint32 NextTakeNumberDigits = ProjectSettings->TakeNumDigits;
|
|
|
|
if (ParseShotName(CurrentSectionName, CurrentShotPrefix, CurrentShotNumber, CurrentTakeNumber, CurrentShotNumberDigits, CurrentTakeNumberDigits) &&
|
|
ParseShotName(NextSectionName, NextShotPrefix, NextShotNumber, NextTakeNumber, NextShotNumberDigits, NextTakeNumberDigits))
|
|
{
|
|
// Valid shot numbers
|
|
if (NextShotNumber != INDEX_NONE && CurrentShotNumber != INDEX_NONE)
|
|
{
|
|
uint32 NewShotNumber = CurrentShotNumber + ProjectSettings->ShotIncrement;
|
|
if (NextShotNumber - CurrentShotNumber > 1)
|
|
{
|
|
NewShotNumber = CurrentShotNumber + ( (NextShotNumber - CurrentShotNumber) / 2); // what if we can't find one? or conflicts with another?
|
|
}
|
|
return ComposeShotName(CurrentShotPrefix, NewShotNumber, ProjectSettings->FirstTakeNumber, CurrentShotNumberDigits, CurrentTakeNumberDigits); // use before or next shot?
|
|
}
|
|
// No shot numbers, but valid take numbers
|
|
else if (NextTakeNumber != INDEX_NONE && CurrentTakeNumber != INDEX_NONE)
|
|
{
|
|
uint32 NewTakeNumber = CurrentTakeNumber + ProjectSettings->ShotIncrement;
|
|
if (NextTakeNumber - CurrentTakeNumber > 1)
|
|
{
|
|
NewTakeNumber = CurrentTakeNumber + ( (NextTakeNumber - CurrentTakeNumber) / 2); // what if we can't find one? or conflicts with another?
|
|
}
|
|
return ComposeShotName(CurrentShotPrefix, INDEX_NONE, NewTakeNumber, CurrentShotNumberDigits, CurrentTakeNumberDigits); // use before or next shot?
|
|
}
|
|
}
|
|
}
|
|
|
|
// Default case
|
|
return ComposeShotName(SubsequencePrefix, ProjectSettings->FirstShotNumber, ProjectSettings->FirstTakeNumber, ProjectSettings->ShotNumDigits, ProjectSettings->TakeNumDigits);
|
|
}
|
|
|
|
UMovieSceneSequence* MovieSceneToolHelpers::CreateSequence(FString& NewSequenceName, FString& NewSequencePath, UMovieSceneSubSection* SectionToDuplicate)
|
|
{
|
|
// Create a new level sequence asset with the appropriate name
|
|
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
|
|
|
UObject* NewAsset = nullptr;
|
|
for (TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
UClass* CurrentClass = *It;
|
|
if (CurrentClass->IsChildOf(UFactory::StaticClass()) && !(CurrentClass->HasAnyClassFlags(CLASS_Abstract)))
|
|
{
|
|
UFactory* Factory = Cast<UFactory>(CurrentClass->GetDefaultObject());
|
|
if (Factory->CanCreateNew() && Factory->ImportPriority >= 0 && Factory->SupportedClass == ULevelSequence::StaticClass())
|
|
{
|
|
if (SectionToDuplicate != nullptr)
|
|
{
|
|
NewAsset = AssetTools.DuplicateAssetWithDialog(NewSequenceName, NewSequencePath, SectionToDuplicate->GetSequence());
|
|
}
|
|
else
|
|
{
|
|
NewAsset = AssetTools.CreateAssetWithDialog(NewSequenceName, NewSequencePath, ULevelSequence::StaticClass(), Factory);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NewAsset == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return Cast<UMovieSceneSequence>(NewAsset);
|
|
}
|
|
|
|
void MovieSceneToolHelpers::GatherTakes(const UMovieSceneSection* Section, TArray<FAssetData>& AssetData, uint32& OutCurrentTakeNumber)
|
|
{
|
|
const UMovieSceneSubSection* SubSection = Cast<const UMovieSceneSubSection>(Section);
|
|
|
|
if (SubSection->GetSequence() == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (FMovieSceneToolsModule::Get().GatherTakes(Section, AssetData, OutCurrentTakeNumber))
|
|
{
|
|
return;
|
|
}
|
|
|
|
FAssetData ShotData(SubSection->GetSequence()->GetOuter());
|
|
|
|
FString ShotPackagePath = ShotData.PackagePath.ToString();
|
|
|
|
FString ShotPrefix;
|
|
uint32 ShotNumber = INDEX_NONE;
|
|
OutCurrentTakeNumber = INDEX_NONE;
|
|
uint32 ShotNumberDigits = 0;
|
|
uint32 TakeNumberDigits = 0;
|
|
|
|
FString SequenceName = SubSection->GetSequence()->GetName();
|
|
ParseShotName(SequenceName, ShotPrefix, ShotNumber, OutCurrentTakeNumber, ShotNumberDigits, TakeNumberDigits);
|
|
|
|
// Gather up all level sequence assets
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
TArray<FAssetData> ObjectList;
|
|
AssetRegistryModule.Get().GetAssetsByClass(ULevelSequence::StaticClass()->GetClassPathName(), ObjectList);
|
|
|
|
for (auto AssetObject : ObjectList)
|
|
{
|
|
FString AssetPackagePath = AssetObject.PackagePath.ToString();
|
|
|
|
if (AssetPackagePath == ShotPackagePath)
|
|
{
|
|
FString AssetShotPrefix;
|
|
uint32 AssetShotNumber = INDEX_NONE;
|
|
uint32 AssetTakeNumber = INDEX_NONE;
|
|
|
|
ParseShotName(AssetObject.AssetName.ToString(), AssetShotPrefix, AssetShotNumber, AssetTakeNumber, ShotNumberDigits, TakeNumberDigits);
|
|
|
|
if (AssetShotPrefix == ShotPrefix && AssetShotNumber == ShotNumber)
|
|
{
|
|
AssetData.Add(AssetObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::GetTakeNumber(const UMovieSceneSection* Section, FAssetData AssetData, uint32& OutTakeNumber)
|
|
{
|
|
if (FMovieSceneToolsModule::Get().GetTakeNumber(Section, AssetData, OutTakeNumber))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const UMovieSceneSubSection* SubSection = Cast<const UMovieSceneSubSection>(Section);
|
|
|
|
FAssetData ShotData(SubSection->GetSequence()->GetOuter());
|
|
|
|
FString ShotPackagePath = ShotData.PackagePath.ToString();
|
|
int32 ShotLastSlashPos = INDEX_NONE;
|
|
ShotPackagePath.FindLastChar(TCHAR('/'), ShotLastSlashPos);
|
|
ShotPackagePath.LeftInline(ShotLastSlashPos, EAllowShrinking::No);
|
|
|
|
FString ShotPrefix;
|
|
uint32 ShotNumber = INDEX_NONE;
|
|
uint32 TakeNumberDummy = INDEX_NONE;
|
|
uint32 ShotNumberDigits = 0;
|
|
uint32 TakeNumberDigits = 0;
|
|
|
|
FString SequenceName = SubSection->GetSequence()->GetName();
|
|
ParseShotName(SequenceName, ShotPrefix, ShotNumber, TakeNumberDummy, ShotNumberDigits, TakeNumberDigits);
|
|
|
|
// Gather up all level sequence assets
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
TArray<FAssetData> ObjectList;
|
|
AssetRegistryModule.Get().GetAssetsByClass(ULevelSequence::StaticClass()->GetClassPathName(), ObjectList);
|
|
|
|
for (auto AssetObject : ObjectList)
|
|
{
|
|
if (AssetObject == AssetData)
|
|
{
|
|
FString AssetPackagePath = AssetObject.PackagePath.ToString();
|
|
int32 AssetLastSlashPos = INDEX_NONE;
|
|
AssetPackagePath.FindLastChar(TCHAR('/'), AssetLastSlashPos);
|
|
AssetPackagePath.LeftInline(AssetLastSlashPos, EAllowShrinking::No);
|
|
|
|
if (AssetPackagePath == ShotPackagePath)
|
|
{
|
|
FString AssetShotPrefix;
|
|
uint32 AssetShotNumber = INDEX_NONE;
|
|
uint32 AssetTakeNumber = INDEX_NONE;
|
|
|
|
ParseShotName(AssetObject.AssetName.ToString(), AssetShotPrefix, AssetShotNumber, AssetTakeNumber, ShotNumberDigits, TakeNumberDigits);
|
|
|
|
if (AssetShotPrefix == ShotPrefix && AssetShotNumber == ShotNumber)
|
|
{
|
|
OutTakeNumber = AssetTakeNumber;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::SetTakeNumber(const UMovieSceneSection* Section, uint32 InTakeNumber)
|
|
{
|
|
return FMovieSceneToolsModule::Get().SetTakeNumber(Section, InTakeNumber);
|
|
}
|
|
|
|
int32 MovieSceneToolHelpers::FindAvailableRowIndex(UMovieSceneTrack* InTrack, UMovieSceneSection* InSection, const TArray<UMovieSceneSection*>& SectionsToDisregard)
|
|
{
|
|
for (int32 RowIndex = 0; RowIndex <= InTrack->GetMaxRowIndex(); ++RowIndex)
|
|
{
|
|
bool bFoundIntersect = false;
|
|
for (UMovieSceneSection* Section : InTrack->GetAllSections())
|
|
{
|
|
if (SectionsToDisregard.Contains(Section))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!Section->HasStartFrame() || !Section->HasEndFrame() || !InSection->HasStartFrame() || !InSection->HasEndFrame())
|
|
{
|
|
bFoundIntersect = true;
|
|
break;
|
|
}
|
|
|
|
if (Section != InSection && Section->GetRowIndex() == RowIndex && Section->GetRange().Overlaps(InSection->GetRange()))
|
|
{
|
|
bFoundIntersect = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bFoundIntersect)
|
|
{
|
|
return RowIndex;
|
|
}
|
|
}
|
|
|
|
return InTrack->GetMaxRowIndex() + 1;
|
|
}
|
|
|
|
|
|
bool MovieSceneToolHelpers::OverlapsSection(UMovieSceneTrack* InTrack, UMovieSceneSection* InSection, const TArray<UMovieSceneSection*>& SectionsToDisregard)
|
|
{
|
|
for (UMovieSceneSection* Section : InTrack->GetAllSections())
|
|
{
|
|
if (SectionsToDisregard.Contains(Section))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!Section->HasStartFrame() || !Section->HasEndFrame() || !InSection->HasStartFrame() || !InSection->HasEndFrame())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (Section != InSection && Section->GetRange().Overlaps(InSection->GetRange()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TSharedRef<SWidget> MovieSceneToolHelpers::MakeEnumComboBox(const UEnum* InEnum, TAttribute<int32> InCurrentValue, SEnumComboBox::FOnEnumSelectionChanged InOnSelectionChanged)
|
|
{
|
|
return SNew(SEnumComboBox, InEnum)
|
|
.CurrentValue(InCurrentValue)
|
|
.ContentPadding(FMargin(2, 0))
|
|
.Font(FAppStyle::GetFontStyle("Sequencer.AnimationOutliner.RegularFont"))
|
|
.OnEnumSelectionChanged(InOnSelectionChanged);
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ShowImportEDLDialog(UMovieScene* InMovieScene, FFrameRate InFrameRate, FString InOpenDirectory)
|
|
{
|
|
TArray<FString> OpenFilenames;
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bOpen = false;
|
|
if (DesktopPlatform)
|
|
{
|
|
FString ExtensionStr;
|
|
ExtensionStr += TEXT("CMX 3600 EDL (*.edl)|*.edl|");
|
|
|
|
bOpen = DesktopPlatform->OpenFileDialog(
|
|
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
|
|
NSLOCTEXT("MovieSceneToolHelpers", "ImportEDL", "Import EDL from...").ToString(),
|
|
InOpenDirectory,
|
|
TEXT(""),
|
|
*ExtensionStr,
|
|
EFileDialogFlags::None,
|
|
OpenFilenames
|
|
);
|
|
}
|
|
if (!bOpen)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!OpenFilenames.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FScopedTransaction Transaction( NSLOCTEXT( "MovieSceneTools", "ImportEDLTransaction", "Import EDL" ) );
|
|
|
|
return MovieSceneTranslatorEDL::ImportEDL(InMovieScene, InFrameRate, OpenFilenames[0]);
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ShowExportEDLDialog(const UMovieScene* InMovieScene, FFrameRate InFrameRate, FString InSaveDirectory, int32 InHandleFrames, FString InMovieExtension)
|
|
{
|
|
TArray<FString> SaveFilenames;
|
|
FString SequenceName = InMovieScene->GetOuter()->GetName();
|
|
|
|
// Pop open a dialog to request the location of the edl
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bSave = false;
|
|
if (DesktopPlatform)
|
|
{
|
|
FString ExtensionStr;
|
|
ExtensionStr += TEXT("CMX 3600 EDL (*.edl)|*.edl|");
|
|
ExtensionStr += TEXT("RV (*.rv)|*.rv|");
|
|
|
|
bSave = DesktopPlatform->SaveFileDialog(
|
|
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
|
|
NSLOCTEXT("MovieSceneTools", "ExportEDL", "Export EDL to...").ToString(),
|
|
InSaveDirectory,
|
|
SequenceName + TEXT(".edl"),
|
|
*ExtensionStr,
|
|
EFileDialogFlags::None,
|
|
SaveFilenames
|
|
);
|
|
}
|
|
if (!bSave)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!SaveFilenames.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (MovieSceneTranslatorEDL::ExportEDL(InMovieScene, InFrameRate, SaveFilenames[0], InHandleFrames, InMovieExtension))
|
|
{
|
|
const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SaveFilenames[0]);
|
|
const FString SaveDirectory = FPaths::GetPath(AbsoluteFilename);
|
|
|
|
FNotificationInfo NotificationInfo(NSLOCTEXT("MovieSceneTools", "EDLExportFinished", "EDL Export finished"));
|
|
NotificationInfo.ExpireDuration = 5.f;
|
|
NotificationInfo.Hyperlink = FSimpleDelegate::CreateStatic( [](FString InDirectory) { FPlatformProcess::ExploreFolder( *InDirectory ); }, SaveDirectory);
|
|
NotificationInfo.HyperlinkText = NSLOCTEXT("MovieSceneTools", "OpenEDLExportFolder", "Open EDL Export Folder...");
|
|
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::MovieSceneTranslatorImport(FMovieSceneImporter* InImporter, UMovieScene* InMovieScene, FFrameRate InFrameRate, FString InOpenDirectory)
|
|
{
|
|
TArray<FString> OpenFilenames;
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bOpen = false;
|
|
if (DesktopPlatform)
|
|
{
|
|
FString FileTypeDescription = InImporter->GetFileTypeDescription().ToString();
|
|
FString DialogTitle = InImporter->GetDialogTitle().ToString();
|
|
|
|
bOpen = DesktopPlatform->OpenFileDialog(
|
|
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
|
|
DialogTitle,
|
|
InOpenDirectory,
|
|
TEXT(""),
|
|
FileTypeDescription,
|
|
EFileDialogFlags::None,
|
|
OpenFilenames
|
|
);
|
|
}
|
|
|
|
if (!bOpen || !OpenFilenames.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FScopedTransaction Transaction(InImporter->GetTransactionDescription());
|
|
|
|
TSharedRef<FMovieSceneTranslatorContext> ImportContext(new FMovieSceneTranslatorContext);
|
|
ImportContext->Init();
|
|
|
|
bool bSuccess = InImporter->Import(InMovieScene, InFrameRate, OpenFilenames[0], ImportContext);
|
|
|
|
// Display any messages in context
|
|
MovieSceneTranslatorLogMessages(InImporter, ImportContext, true);
|
|
|
|
// Roll back transaction when import fails.
|
|
if (!bSuccess)
|
|
{
|
|
Transaction.Cancel();
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::MovieSceneTranslatorExport(FMovieSceneExporter* InExporter, const UMovieScene* InMovieScene, const FMovieSceneCaptureSettings& Settings)
|
|
{
|
|
if (InExporter == nullptr || InMovieScene == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FString SaveDirectory = FPaths::ConvertRelativePathToFull(Settings.OutputDirectory.Path);
|
|
int32 HandleFrames = Settings.HandleFrames;
|
|
// @todo: generate filename based on filename format, currently outputs {shot}.avi
|
|
FString FilenameFormat = Settings.OutputFormat;
|
|
FFrameRate FrameRate = Settings.GetFrameRate();
|
|
uint32 ResX = Settings.Resolution.ResX;
|
|
uint32 ResY = Settings.Resolution.ResY;
|
|
FString MovieExtension = Settings.MovieExtension;
|
|
|
|
TArray<FString> SaveFilenames;
|
|
FString SequenceName = InMovieScene->GetOuter()->GetName();
|
|
|
|
// Pop open a dialog to request the location of the edl
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bSave = false;
|
|
if (DesktopPlatform)
|
|
{
|
|
FString FileTypeDescription = InExporter->GetFileTypeDescription().ToString();
|
|
FString DialogTitle = InExporter->GetDialogTitle().ToString();
|
|
FString FileExtension = InExporter->GetDefaultFileExtension().ToString();
|
|
|
|
bSave = DesktopPlatform->SaveFileDialog(
|
|
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
|
|
DialogTitle,
|
|
SaveDirectory,
|
|
SequenceName + TEXT(".") + FileExtension,
|
|
FileTypeDescription,
|
|
EFileDialogFlags::None,
|
|
SaveFilenames
|
|
);
|
|
}
|
|
|
|
if (!bSave || !SaveFilenames.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TSharedRef<FMovieSceneTranslatorContext> ExportContext(new FMovieSceneTranslatorContext);
|
|
ExportContext->Init();
|
|
|
|
bool bSuccess = InExporter->Export(InMovieScene, FilenameFormat, FrameRate, ResX, ResY, HandleFrames, SaveFilenames[0], ExportContext, MovieExtension);
|
|
|
|
// Display any messages in context
|
|
MovieSceneTranslatorLogMessages(InExporter, ExportContext, true);
|
|
|
|
if (bSuccess)
|
|
{
|
|
const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SaveFilenames[0]);
|
|
const FString ActualSaveDirectory = FPaths::GetPath(AbsoluteFilename);
|
|
|
|
FNotificationInfo NotificationInfo(InExporter->GetNotificationExportFinished());
|
|
NotificationInfo.ExpireDuration = 5.f;
|
|
NotificationInfo.Hyperlink = FSimpleDelegate::CreateStatic([](FString InDirectory) { FPlatformProcess::ExploreFolder(*InDirectory); }, ActualSaveDirectory);
|
|
NotificationInfo.HyperlinkText = InExporter->GetNotificationHyperlinkText();
|
|
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
void MovieSceneToolHelpers::MovieSceneTranslatorLogMessages(FMovieSceneTranslator *InTranslator, TSharedRef<FMovieSceneTranslatorContext> InContext, bool bDisplayMessages)
|
|
{
|
|
if (InTranslator == nullptr || InContext->GetMessages().Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Clear any old messages after an import or export
|
|
const FName LogTitle = InTranslator->GetMessageLogWindowTitle();
|
|
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
|
|
TSharedRef<IMessageLogListing> LogListing = MessageLogModule.GetLogListing(LogTitle);
|
|
LogListing->SetLabel(InTranslator->GetMessageLogLabel());
|
|
LogListing->ClearMessages();
|
|
|
|
for (TSharedRef<FTokenizedMessage> Message : InContext->GetMessages())
|
|
{
|
|
LogListing->AddMessage(Message);
|
|
}
|
|
|
|
if (bDisplayMessages)
|
|
{
|
|
MessageLogModule.OpenMessageLog(LogTitle);
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::MovieSceneTranslatorLogOutput(FMovieSceneTranslator *InTranslator, TSharedRef<FMovieSceneTranslatorContext> InContext)
|
|
{
|
|
if (InTranslator == nullptr || InContext->GetMessages().Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (TSharedRef<FTokenizedMessage> Message : InContext->GetMessages())
|
|
{
|
|
if (Message->GetSeverity() == EMessageSeverity::Error)
|
|
{
|
|
UE_LOG(LogMovieScene, Error, TEXT("%s"), *Message->ToText().ToString());
|
|
}
|
|
else if (Message->GetSeverity() == EMessageSeverity::Warning)
|
|
{
|
|
UE_LOG(LogMovieScene, Warning, TEXT("%s"), *Message->ToText().ToString());
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
static FGuid GetHandleToObject(UObject* InObject, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, bool bCreateIfMissing = true)
|
|
{
|
|
UMovieScene* MovieScene = InSequence->GetMovieScene();
|
|
|
|
// Attempt to resolve the object through the movie scene instance first,
|
|
FGuid PropertyOwnerGuid = FGuid();
|
|
if (InObject != nullptr && !MovieScene->IsReadOnly())
|
|
{
|
|
FGuid ObjectGuid = Player->FindObjectId(*InObject, TemplateID);
|
|
if (ObjectGuid.IsValid())
|
|
{
|
|
// Check here for spawnable otherwise spawnables get recreated as possessables, which doesn't make sense
|
|
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectGuid);
|
|
if (Spawnable)
|
|
{
|
|
PropertyOwnerGuid = ObjectGuid;
|
|
}
|
|
else
|
|
{
|
|
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectGuid);
|
|
if (Possessable)
|
|
{
|
|
PropertyOwnerGuid = ObjectGuid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (PropertyOwnerGuid.IsValid())
|
|
{
|
|
return PropertyOwnerGuid;
|
|
}
|
|
|
|
if (bCreateIfMissing)
|
|
{
|
|
// Otherwise, create a possessable for this object. Note this will handle creating the parent possessables if this is a component.
|
|
PropertyOwnerGuid = Player->CreateBinding(InSequence, InObject);
|
|
}
|
|
|
|
return PropertyOwnerGuid;
|
|
}
|
|
|
|
template<typename TrackType, typename ChannelType>
|
|
bool ImportFBXPropertyToPropertyTrack(UMovieScene* MovieScene, const FGuid PropertyOwnerGuid, const FMovieSceneToolsFbxSettings& FbxSetting, FRichCurve& Source)
|
|
{
|
|
TrackType* PropertyTrack = MovieScene->FindTrack<TrackType>(PropertyOwnerGuid, *FbxSetting.PropertyPath.PropertyName);
|
|
if (!PropertyTrack)
|
|
{
|
|
MovieScene->Modify();
|
|
PropertyTrack = MovieScene->AddTrack<TrackType>(PropertyOwnerGuid);
|
|
PropertyTrack->SetPropertyNameAndPath(*FbxSetting.PropertyPath.PropertyName, *FbxSetting.PropertyPath.PropertyName);
|
|
}
|
|
|
|
if (!PropertyTrack)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
PropertyTrack->Modify();
|
|
PropertyTrack->RemoveAllAnimationData();
|
|
|
|
FFrameRate FrameRate = PropertyTrack->template GetTypedOuter<UMovieScene>()->GetTickResolution();
|
|
|
|
bool bSectionAdded = false;
|
|
UMovieSceneSection* Section = Cast<UMovieSceneSection>(PropertyTrack->FindOrAddSection(0, bSectionAdded));
|
|
if (!Section)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Section->Modify();
|
|
|
|
if (bSectionAdded)
|
|
{
|
|
Section->SetRange(TRange<FFrameNumber>::All());
|
|
}
|
|
|
|
ChannelType* Channel = Section->GetChannelProxy().GetChannel<ChannelType>(0);
|
|
TMovieSceneChannelData<typename ChannelType::ChannelValueType> ChannelData = Channel->GetData();
|
|
|
|
ChannelData.Reset();
|
|
double DecimalRate = FrameRate.AsDecimal();
|
|
|
|
for (auto SourceIt = Source.GetKeyHandleIterator(); SourceIt; ++SourceIt)
|
|
{
|
|
FRichCurveKey &Key = Source.GetKey(*SourceIt);
|
|
float ArriveTangent = Key.ArriveTangent;
|
|
FKeyHandle PrevKeyHandle = Source.GetPreviousKey(*SourceIt);
|
|
if (Source.IsKeyHandleValid(PrevKeyHandle))
|
|
{
|
|
FRichCurveKey &PrevKey = Source.GetKey(PrevKeyHandle);
|
|
ArriveTangent = ArriveTangent / (Key.Time - PrevKey.Time);
|
|
|
|
}
|
|
float LeaveTangent = Key.LeaveTangent;
|
|
FKeyHandle NextKeyHandle = Source.GetNextKey(*SourceIt);
|
|
if (Source.IsKeyHandleValid(NextKeyHandle))
|
|
{
|
|
FRichCurveKey &NextKey = Source.GetKey(NextKeyHandle);
|
|
LeaveTangent = LeaveTangent / (NextKey.Time - Key.Time);
|
|
}
|
|
|
|
FFrameNumber KeyTime = (Key.Time * FrameRate).RoundToFrame();
|
|
MovieSceneToolHelpers::SetOrAddKey(ChannelData, KeyTime, Key.Value, ArriveTangent, LeaveTangent,
|
|
Key.InterpMode, Key.TangentMode, FrameRate, Key.TangentWeightMode,
|
|
Key.ArriveTangentWeight, Key.LeaveTangentWeight);
|
|
}
|
|
|
|
Channel->AutoSetTangents();
|
|
|
|
const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault<UMovieSceneUserImportFBXSettings>();
|
|
|
|
if (ImportFBXSettings->bReduceKeys)
|
|
{
|
|
FKeyDataOptimizationParams Params;
|
|
Params.Tolerance = ImportFBXSettings->ReduceKeysTolerance;
|
|
Params.DisplayRate = FrameRate;
|
|
Params.bAutoSetInterpolation = true; //we use this to perform the AutoSetTangents after the keys are reduced.
|
|
Channel->Optimize(Params);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Static function to handle applying uniform scale data to keys inside of camera imports that are distance based.
|
|
void ApplyUniformScaleToCameraKeys(const FString& AnimatedPropertyName, FRichCurve& Source)
|
|
{
|
|
const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault<UMovieSceneUserImportFBXSettings>();
|
|
|
|
// This is the only property right now.
|
|
if (AnimatedPropertyName == "FocusDistance")
|
|
{
|
|
for (int i=0; i<Source.Keys.Num(); i++)
|
|
{
|
|
Source.Keys[i].Value = Source.Keys[i].Value * ImportFBXSettings->ImportUniformScale;
|
|
}
|
|
}
|
|
};
|
|
|
|
bool ImportFBXProperty(FString NodeName, FString AnimatedPropertyName, FGuid ObjectBinding, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID)
|
|
{
|
|
UMovieScene* MovieScene = InSequence->GetMovieScene();
|
|
|
|
const int32 ChannelIndex = 0;
|
|
const int32 CompositeIndex = 0;
|
|
FRichCurve Source;
|
|
const bool bNegative = false;
|
|
CurveAPI.GetCurveDataForSequencer(NodeName, AnimatedPropertyName, ChannelIndex, CompositeIndex, Source, bNegative);
|
|
|
|
// Modify distance based camera curve info to account for Uniform Scale.
|
|
ApplyUniformScaleToCameraKeys(AnimatedPropertyName, Source);
|
|
|
|
// First, see if any of the custom importers can import this named property
|
|
if (FMovieSceneToolsModule::Get().ImportAnimatedProperty(AnimatedPropertyName, Source, ObjectBinding, MovieScene))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault<UMovieSceneToolsProjectSettings>();
|
|
|
|
TArrayView<TWeakObjectPtr<>> BoundObjects = Player->FindBoundObjects(ObjectBinding, TemplateID);
|
|
|
|
for (auto FbxSetting : ProjectSettings->FbxSettings)
|
|
{
|
|
if (FCString::Strcmp(*FbxSetting.FbxPropertyName.ToUpper(), *AnimatedPropertyName.ToUpper()) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (TWeakObjectPtr<>& WeakObject : BoundObjects)
|
|
{
|
|
UObject* FoundObject = WeakObject.Get();
|
|
|
|
if (!FoundObject)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UObject* PropertyOwner = FoundObject;
|
|
if (!FbxSetting.PropertyPath.ComponentName.IsEmpty())
|
|
{
|
|
PropertyOwner = FindObjectFast<UObject>(FoundObject, *FbxSetting.PropertyPath.ComponentName);
|
|
}
|
|
|
|
if (!PropertyOwner)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FGuid PropertyOwnerGuid = GetHandleToObject(PropertyOwner, InSequence, Player, TemplateID);
|
|
if (!PropertyOwnerGuid.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!PropertyOwnerGuid.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool bImported = false;
|
|
switch (FbxSetting.PropertyType)
|
|
{
|
|
case EMovieSceneToolsPropertyTrackType::FloatTrack:
|
|
bImported = ImportFBXPropertyToPropertyTrack<UMovieSceneFloatTrack, FMovieSceneFloatChannel>(MovieScene, PropertyOwnerGuid, FbxSetting, Source);
|
|
break;
|
|
case EMovieSceneToolsPropertyTrackType::DoubleTrack:
|
|
bImported = ImportFBXPropertyToPropertyTrack<UMovieSceneDoubleTrack, FMovieSceneDoubleChannel>(MovieScene, PropertyOwnerGuid, FbxSetting, Source);
|
|
break;
|
|
}
|
|
if (bImported)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void MovieSceneToolHelpers::LockCameraActorToViewport(const TSharedPtr<ISequencer>& Sequencer, ACameraActor* CameraActor)
|
|
{
|
|
Sequencer->SetPerspectiveViewportCameraCutEnabled(false);
|
|
|
|
// Lock the viewport to this camera
|
|
if (GCurrentLevelEditingViewportClient && CameraActor && CameraActor->GetLevel())
|
|
{
|
|
GCurrentLevelEditingViewportClient->SetCinematicActorLock(nullptr);
|
|
GCurrentLevelEditingViewportClient->SetActorLock(CameraActor);
|
|
GCurrentLevelEditingViewportClient->bLockedCameraView = true;
|
|
GCurrentLevelEditingViewportClient->UpdateViewForLockedActor();
|
|
GCurrentLevelEditingViewportClient->Invalidate();
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::CreateCameraCutSectionForCamera(UMovieScene* OwnerMovieScene, FGuid CameraGuid, FFrameNumber FrameNumber)
|
|
{
|
|
// If there's a cinematic shot track, no need to set this camera to a shot
|
|
UMovieSceneTrack* CinematicShotTrack = OwnerMovieScene->FindTrack(UMovieSceneCinematicShotTrack::StaticClass());
|
|
if (CinematicShotTrack)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UMovieSceneTrack* CameraCutTrack = OwnerMovieScene->GetCameraCutTrack();
|
|
|
|
// If there's a camera cut track with at least one section, no need to change the section
|
|
if (CameraCutTrack && CameraCutTrack->GetAllSections().Num() > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!CameraCutTrack)
|
|
{
|
|
CameraCutTrack = OwnerMovieScene->AddCameraCutTrack(UMovieSceneCameraCutTrack::StaticClass());
|
|
}
|
|
|
|
if (CameraCutTrack)
|
|
{
|
|
UMovieSceneSection* Section = MovieSceneHelpers::FindSectionAtTime(CameraCutTrack->GetAllSections(), FrameNumber);
|
|
UMovieSceneCameraCutSection* CameraCutSection = Cast<UMovieSceneCameraCutSection>(Section);
|
|
|
|
if (CameraCutSection)
|
|
{
|
|
CameraCutSection->Modify();
|
|
CameraCutSection->SetCameraGuid(CameraGuid);
|
|
}
|
|
else
|
|
{
|
|
CameraCutTrack->Modify();
|
|
|
|
UMovieSceneCameraCutSection* NewSection = Cast<UMovieSceneCameraCutSection>(CameraCutTrack->CreateNewSection());
|
|
NewSection->SetRange(OwnerMovieScene->GetPlaybackRange());
|
|
NewSection->SetCameraGuid(CameraGuid);
|
|
CameraCutTrack->AddSection(*NewSection);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename ChannelType>
|
|
void ImportTransformChannelToBezierChannel(const FRichCurve& Source, ChannelType* Dest, FFrameRate DestFrameRate, bool bNegateTangents, bool bClearChannel,FFrameNumber StartFrame = 0, bool bNegateValue = false)
|
|
{
|
|
// If there are no keys, don't clear the existing channel
|
|
if (!Source.GetNumKeys())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TMovieSceneChannelData<typename ChannelType::ChannelValueType> ChannelData = Dest->GetData();
|
|
|
|
if (bClearChannel)
|
|
{
|
|
ChannelData.Reset();
|
|
}
|
|
for (auto SourceIt = Source.GetKeyHandleIterator(); SourceIt; ++SourceIt)
|
|
{
|
|
const FRichCurveKey Key = Source.GetKey(*SourceIt);
|
|
float ArriveTangent = Key.ArriveTangent;
|
|
float LeaveTangent = Key.LeaveTangent;
|
|
if (bNegateTangents)
|
|
{
|
|
ArriveTangent = -ArriveTangent;
|
|
LeaveTangent = -LeaveTangent;
|
|
}
|
|
|
|
FFrameNumber KeyTime = (Key.Time * DestFrameRate).RoundToFrame();
|
|
float Value = !bNegateValue ? Key.Value : -Key.Value;
|
|
MovieSceneToolHelpers::SetOrAddKey(ChannelData, KeyTime + StartFrame, Value, ArriveTangent, LeaveTangent,
|
|
Key.InterpMode, Key.TangentMode, DestFrameRate, Key.TangentWeightMode,
|
|
Key.ArriveTangentWeight, Key.LeaveTangentWeight);
|
|
}
|
|
|
|
Dest->AutoSetTangents();
|
|
|
|
const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault<UMovieSceneUserImportFBXSettings>();
|
|
if (ImportFBXSettings->bReduceKeys)
|
|
{
|
|
FKeyDataOptimizationParams Params;
|
|
Params.Tolerance = ImportFBXSettings->ReduceKeysTolerance;
|
|
Params.DisplayRate = DestFrameRate;
|
|
Dest->Optimize(Params);
|
|
}
|
|
}
|
|
|
|
void ImportTransformChannelToDouble(const FRichCurve& Source, FMovieSceneDoubleChannel* Dest, FFrameRate DestFrameRate, bool bNegateTangents, bool bClearChannel, FFrameNumber StartFrame = 0, bool bNegateValue = false)
|
|
{
|
|
ImportTransformChannelToBezierChannel(Source, Dest, DestFrameRate, bNegateTangents, bClearChannel, StartFrame, bNegateValue);
|
|
}
|
|
|
|
void ImportTransformChannelToFloat(const FRichCurve& Source, FMovieSceneFloatChannel* Dest, FFrameRate DestFrameRate, bool bNegateTangents, bool bClearChannel, FFrameNumber StartFrame = 0, bool bNegateValue = false)
|
|
{
|
|
ImportTransformChannelToBezierChannel(Source, Dest, DestFrameRate, bNegateTangents, bClearChannel, StartFrame, bNegateValue);
|
|
}
|
|
|
|
void ImportTransformChannelToBool(const FRichCurve& Source, FMovieSceneBoolChannel* Dest, FFrameRate DestFrameRate, bool bClearChannel, FFrameNumber StartFrame)
|
|
{
|
|
// If there are no keys, don't clear the existing channel
|
|
if (!Source.GetNumKeys())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TMovieSceneChannelData<bool> ChannelData = Dest->GetData();
|
|
|
|
if (bClearChannel)
|
|
{
|
|
ChannelData.Reset();
|
|
}
|
|
for (auto SourceIt = Source.GetKeyHandleIterator(); SourceIt; ++SourceIt)
|
|
{
|
|
const FRichCurveKey Key = Source.GetKey(*SourceIt);
|
|
bool bValue = Key.Value != 0.0f ? true : false;
|
|
|
|
FFrameNumber KeyTime = (Key.Time * DestFrameRate).RoundToFrame();
|
|
|
|
KeyTime += StartFrame;
|
|
if (ChannelData.FindKey(KeyTime) == INDEX_NONE)
|
|
{
|
|
ChannelData.AddKey(KeyTime, bValue);
|
|
} //todo need to do a set here?
|
|
}
|
|
}
|
|
|
|
void ImportTransformChannelToEnum(const FRichCurve& Source, FMovieSceneByteChannel* Dest, FFrameRate DestFrameRate, bool bClearChannel, FFrameNumber StartFrame)
|
|
{
|
|
// If there are no keys, don't clear the existing channel
|
|
if (!Source.GetNumKeys())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TMovieSceneChannelData<uint8> ChannelData = Dest->GetData();
|
|
|
|
if (bClearChannel)
|
|
{
|
|
ChannelData.Reset();
|
|
}
|
|
for (auto SourceIt = Source.GetKeyHandleIterator(); SourceIt; ++SourceIt)
|
|
{
|
|
const FRichCurveKey Key = Source.GetKey(*SourceIt);
|
|
uint8 Value = (uint8)Key.Value;
|
|
|
|
FFrameNumber KeyTime = (Key.Time * DestFrameRate).RoundToFrame();
|
|
|
|
KeyTime += StartFrame;
|
|
if (ChannelData.FindKey(KeyTime) == INDEX_NONE)
|
|
{
|
|
ChannelData.AddKey(KeyTime, Value);
|
|
} //todo need to do a set here?
|
|
}
|
|
}
|
|
|
|
|
|
void ImportTransformChannelToInteger(const FRichCurve& Source, FMovieSceneIntegerChannel* Dest, FFrameRate DestFrameRate, bool bClearChannel, FFrameNumber StartFrame)
|
|
{
|
|
// If there are no keys, don't clear the existing channel
|
|
if (!Source.GetNumKeys())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TMovieSceneChannelData<int32> ChannelData = Dest->GetData();
|
|
|
|
if (bClearChannel)
|
|
{
|
|
ChannelData.Reset();
|
|
}
|
|
for (auto SourceIt = Source.GetKeyHandleIterator(); SourceIt; ++SourceIt)
|
|
{
|
|
const FRichCurveKey Key = Source.GetKey(*SourceIt);
|
|
int32 Value = (int32)Key.Value;
|
|
|
|
FFrameNumber KeyTime = (Key.Time * DestFrameRate).RoundToFrame();
|
|
|
|
KeyTime += StartFrame;
|
|
if (ChannelData.FindKey(KeyTime) == INDEX_NONE)
|
|
{
|
|
ChannelData.AddKey(KeyTime, Value);
|
|
} //todo need to do a set here?
|
|
}
|
|
}
|
|
|
|
void SetChannelValue(FMovieSceneDoubleChannel* DoubleChannel, FMovieSceneFloatChannel* FloatChannel, FMovieSceneBoolChannel *BoolChannel, FMovieSceneByteChannel *EnumChannel, FMovieSceneIntegerChannel* IntegerChannel,
|
|
const FFrameRate& FrameRate, const FFrameNumber& StartFrame,
|
|
FControlRigChannelEnum ChannelEnum, UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings,
|
|
const FTransform& DefaultTransform,
|
|
const FRichCurve& TranslationX, const FRichCurve& TranslationY, const FRichCurve& TranslationZ,
|
|
const FRichCurve& EulerRotationX, const FRichCurve& EulerRotationY, const FRichCurve& EulerRotationZ,
|
|
const FRichCurve& ScaleX, const FRichCurve& ScaleY, const FRichCurve& ScaleZ)
|
|
{
|
|
const FRichCurve* TransformCurves[9] = {&TranslationX, &TranslationY, &TranslationZ, &EulerRotationX, &EulerRotationY, &EulerRotationZ, &ScaleX, &ScaleY, &ScaleZ};
|
|
|
|
FVector Location = DefaultTransform.GetLocation(), Rotation = DefaultTransform.GetRotation().Euler(), Scale3D = DefaultTransform.GetScale3D();
|
|
const double DefaultTransformValues[9] = {Location[0], Location[1], Location[2], Rotation[0], Rotation[1], Rotation[2], Scale3D[0], Scale3D[1], Scale3D[2]};
|
|
|
|
const FRichCurve *RichCurve = &TranslationX;
|
|
float DefaultValue = Location.X;
|
|
bool bNegate = false;
|
|
|
|
uint8 ChannelIndex = 0; // Default Channel is TX
|
|
|
|
if (const FControlToTransformMappings* ChannelMapping = ImportFBXControlRigSettings->ControlChannelMappings.FindByPredicate(
|
|
[ChannelEnum](const FControlToTransformMappings& Mapping) { return ChannelEnum == Mapping.ControlChannel; }))
|
|
{
|
|
bNegate = ChannelMapping->bNegate;
|
|
|
|
ChannelIndex = (uint8)ChannelMapping->FBXChannel;
|
|
}
|
|
else
|
|
{
|
|
const uint8 ChannelEnumIndex = (uint8)ChannelEnum;
|
|
|
|
// 2DX, Bool and other default to TX
|
|
if (ChannelEnum == FControlRigChannelEnum::Vector2DY)
|
|
{
|
|
ChannelIndex = 1;
|
|
}
|
|
else if (ChannelEnumIndex >= (uint8)FControlRigChannelEnum::PositionX && ChannelEnumIndex <= (uint8)FControlRigChannelEnum::ScaleZ)
|
|
{
|
|
ChannelIndex = ChannelEnumIndex - (uint8)FControlRigChannelEnum::PositionX;
|
|
}
|
|
}
|
|
|
|
if (ChannelIndex < 9)
|
|
{
|
|
DefaultValue = DefaultTransformValues[ChannelIndex];
|
|
RichCurve = TransformCurves[ChannelIndex];
|
|
}
|
|
|
|
if (ChannelEnum == FControlRigChannelEnum::Bool && BoolChannel)
|
|
{
|
|
const bool Default = DefaultValue != 0.0 ? !bNegate : bNegate;
|
|
|
|
BoolChannel->SetDefault(Default);
|
|
ImportTransformChannelToBool(*RichCurve, BoolChannel, FrameRate, false, StartFrame);
|
|
}
|
|
else if (ChannelEnum == FControlRigChannelEnum::Enum && EnumChannel)
|
|
{
|
|
const uint8 Default = (uint8)(bNegate ? -DefaultValue : DefaultValue);
|
|
EnumChannel->SetDefault(Default);
|
|
ImportTransformChannelToEnum(*RichCurve, EnumChannel, FrameRate, false, StartFrame);
|
|
}
|
|
else if (ChannelEnum == FControlRigChannelEnum::Integer && IntegerChannel)
|
|
{
|
|
const int32 Default = (int32)(bNegate ? -DefaultValue : DefaultValue);
|
|
IntegerChannel->SetDefault(Default);
|
|
ImportTransformChannelToInteger(*RichCurve, IntegerChannel, FrameRate, false, StartFrame);
|
|
}
|
|
else if (FloatChannel || DoubleChannel)
|
|
{
|
|
if (*RichCurve == TranslationY)
|
|
{
|
|
bNegate = !bNegate;
|
|
}
|
|
|
|
const float Default = bNegate ? -DefaultValue : DefaultValue;
|
|
if (FloatChannel)
|
|
{
|
|
FloatChannel->SetDefault(Default);
|
|
ImportTransformChannelToFloat(*RichCurve, FloatChannel, FrameRate, bNegate, false, StartFrame, bNegate);
|
|
}
|
|
else if (DoubleChannel)
|
|
{
|
|
DoubleChannel->SetDefault(Default);
|
|
ImportTransformChannelToDouble(*RichCurve, DoubleChannel, FrameRate, bNegate, false, StartFrame, bNegate);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool ImportFBXNonTransformCurvesToChannels(const FString& NodeName, const UMovieSceneUserImportFBXSettings* ImportFBXSettings,UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings,
|
|
const FFrameNumber StartFrame, const FFrameRate FrameRate, FRigControlFBXNodeAndChannels& NodeAndChannels, UnFbx::FFbxCurvesAPI& CurveAPI)
|
|
{
|
|
FbxNode* Node = CurveAPI.Scene->GetRootNode()->FindChild(TCHAR_TO_UTF8(*NodeName));
|
|
if (!Node)
|
|
{
|
|
return false;
|
|
}
|
|
FRichCurve EmptyDefaultCurve;
|
|
|
|
FbxProperty Property = Node->FindProperty(TCHAR_TO_UTF8(*NodeAndChannels.ControlName.ToString()));
|
|
if (!Property.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
EmptyDefaultCurve.SetDefaultValue(Property.Get<float>());
|
|
|
|
TMap<FName, FRichCurve> Curves;
|
|
CurveAPI.GetConvertedNonTransformCurveData(NodeName, true, ImportFBXSettings->ImportUniformScale, Curves);
|
|
|
|
FRichCurve* Curve = Curves.Find(NodeAndChannels.ControlName);
|
|
if (!Curve)
|
|
{
|
|
Curve = &EmptyDefaultCurve;
|
|
}
|
|
|
|
const float CurveDefault = Curve->GetDefaultValue();
|
|
|
|
auto ShouldNegateChannel = [ImportFBXControlRigSettings](FControlRigChannelEnum ChannelType)
|
|
{
|
|
const FControlToTransformMappings* ControlMapping = ImportFBXControlRigSettings->ControlChannelMappings.FindByPredicate(
|
|
[ChannelType](const FControlToTransformMappings& Mapping)
|
|
{
|
|
return Mapping.ControlChannel == ChannelType;
|
|
}
|
|
);
|
|
return ControlMapping && ControlMapping->bNegate;
|
|
};
|
|
|
|
if (!NodeAndChannels.BoolChannels.IsEmpty())
|
|
{
|
|
const bool bNegate = ShouldNegateChannel(FControlRigChannelEnum::Bool);
|
|
const bool ChannelDefault = bNegate ? CurveDefault == 0.0 : CurveDefault != 0.0;
|
|
|
|
NodeAndChannels.BoolChannels[0]->SetDefault(ChannelDefault);
|
|
ImportTransformChannelToBool(*Curve, NodeAndChannels.BoolChannels[0], FrameRate, false, StartFrame);
|
|
}
|
|
else if (!NodeAndChannels.EnumChannels.IsEmpty())
|
|
{
|
|
const bool bNegate = ShouldNegateChannel(FControlRigChannelEnum::Enum);
|
|
const uint8 ChannelDefault = bNegate ? -(uint8)CurveDefault : (uint8)CurveDefault;
|
|
|
|
NodeAndChannels.EnumChannels[0]->SetDefault(ChannelDefault);
|
|
ImportTransformChannelToEnum(*Curve, NodeAndChannels.EnumChannels[0], FrameRate, false, StartFrame);
|
|
}
|
|
else if (!NodeAndChannels.IntegerChannels.IsEmpty())
|
|
{
|
|
const bool bNegate = ShouldNegateChannel(FControlRigChannelEnum::Integer);
|
|
const int32 ChannelDefault = bNegate ? -(int32)CurveDefault : (int32)CurveDefault;
|
|
NodeAndChannels.IntegerChannels[0]->SetDefault(ChannelDefault);
|
|
|
|
ImportTransformChannelToInteger(*Curve, NodeAndChannels.IntegerChannels[0], FrameRate, false, StartFrame);
|
|
}
|
|
else if (!NodeAndChannels.FloatChannels.IsEmpty())
|
|
{
|
|
const bool bNegate = ShouldNegateChannel(FControlRigChannelEnum::Integer);
|
|
const float ChannelDefault = bNegate ? -CurveDefault : CurveDefault;
|
|
NodeAndChannels.FloatChannels[0]->SetDefault(ChannelDefault);
|
|
|
|
ImportTransformChannelToFloat(*Curve, NodeAndChannels.FloatChannels[0], FrameRate, false, false, StartFrame, bNegate);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//if one channel goes to Y
|
|
//if two channel go to X Y
|
|
//if three channel to to x y z
|
|
// if 9 due full
|
|
static bool ImportFBXTransformToChannels(FString NodeName, const UMovieSceneUserImportFBXSettings* ImportFBXSettings,UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings,
|
|
const FFrameNumber StartFrame, const FFrameRate FrameRate, FRigControlFBXNodeAndChannels& NodeAndChannels, UnFbx::FFbxCurvesAPI& CurveAPI)
|
|
{
|
|
TArray<FMovieSceneDoubleChannel*>& DoubleChannels = NodeAndChannels.DoubleChannels;
|
|
TArray<FMovieSceneFloatChannel*>& FloatChannels = NodeAndChannels.FloatChannels;
|
|
TArray<FMovieSceneBoolChannel*>& BoolChannels = NodeAndChannels.BoolChannels;
|
|
TArray<FMovieSceneByteChannel*>& EnumChannels = NodeAndChannels.EnumChannels;
|
|
TArray<FMovieSceneIntegerChannel*>& IntegerChannels = NodeAndChannels.IntegerChannels;
|
|
|
|
// Look for transforms explicitly
|
|
FRichCurve Transform[9];
|
|
FTransform DefaultTransform;
|
|
const bool bUseSequencerCurve = true;
|
|
CurveAPI.GetConvertedTransformCurveData(NodeName, Transform[0], Transform[1], Transform[2], Transform[3], Transform[4], Transform[5], Transform[6], Transform[7], Transform[8], DefaultTransform, bUseSequencerCurve, ImportFBXSettings->ImportUniformScale);
|
|
|
|
FVector Location = DefaultTransform.GetLocation(), Rotation = DefaultTransform.GetRotation().Euler(), Scale3D = DefaultTransform.GetScale3D();
|
|
const double DefaultTransformValues[] = {Location[0], Location[1], Location[2], Rotation[0], Rotation[1], Rotation[2], Scale3D[0], Scale3D[1], Scale3D[2]};
|
|
|
|
|
|
//For non-transforms we need to re-negate the Y since it happens automatically(todo double check.).
|
|
//But then if we negate we need to re-re-negate... so leave it alone.
|
|
|
|
if (BoolChannels.Num() == 1)
|
|
{
|
|
FControlRigChannelEnum Channel = FControlRigChannelEnum::Bool;
|
|
SetChannelValue(nullptr, nullptr, BoolChannels[0], nullptr,nullptr, FrameRate, StartFrame,
|
|
Channel, ImportFBXControlRigSettings, DefaultTransform,
|
|
Transform[0], Transform[1], Transform[2], Transform[3],
|
|
Transform[4], Transform[5], Transform[6], Transform[7], Transform[8]);
|
|
}
|
|
|
|
if (EnumChannels.Num() == 1)
|
|
{
|
|
FControlRigChannelEnum Channel = FControlRigChannelEnum::Enum;
|
|
SetChannelValue(nullptr, nullptr, nullptr, EnumChannels[0], nullptr, FrameRate, StartFrame,
|
|
Channel, ImportFBXControlRigSettings, DefaultTransform,
|
|
Transform[0], Transform[1], Transform[2], Transform[3],
|
|
Transform[4], Transform[5], Transform[6], Transform[7], Transform[8]);
|
|
}
|
|
|
|
if (IntegerChannels.Num() == 1)
|
|
{
|
|
FControlRigChannelEnum Channel = FControlRigChannelEnum::Integer;
|
|
SetChannelValue(nullptr, nullptr, nullptr, nullptr, IntegerChannels[0], FrameRate, StartFrame,
|
|
Channel, ImportFBXControlRigSettings,DefaultTransform,
|
|
Transform[0], Transform[1], Transform[2], Transform[3],
|
|
Transform[4], Transform[5], Transform[6], Transform[7], Transform[8]);
|
|
}
|
|
|
|
int ChannelEnumIndex = INDEX_NONE;
|
|
|
|
if (FloatChannels.Num() == 1)
|
|
{
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::Float;
|
|
}
|
|
else if (FloatChannels.Num() == 2)
|
|
{
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::Vector2DX;
|
|
}
|
|
else if (FloatChannels.Num() == 3)
|
|
{
|
|
if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Position)
|
|
{
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::PositionX;
|
|
}
|
|
else if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Rotator)
|
|
{
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::RotatorX;
|
|
}
|
|
else if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Scale)
|
|
{
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::ScaleX;
|
|
}
|
|
}
|
|
if (ChannelEnumIndex != INDEX_NONE)
|
|
{
|
|
for (int i = 0; i < FloatChannels.Num(); i++)
|
|
{
|
|
const FControlRigChannelEnum ChannelEnum = (FControlRigChannelEnum)(ChannelEnumIndex + i);
|
|
SetChannelValue(nullptr, FloatChannels[i], nullptr, nullptr, nullptr,
|
|
FrameRate, StartFrame, ChannelEnum, ImportFBXControlRigSettings, DefaultTransform,
|
|
Transform[0], Transform[1], Transform[2], Transform[3],
|
|
Transform[4], Transform[5], Transform[6], Transform[7], Transform[8]);
|
|
}
|
|
}
|
|
else if (FloatChannels.Num() == 9 || FloatChannels.Num() == 6)
|
|
{
|
|
for (int i = 0; i < FloatChannels.Num(); i++)
|
|
{
|
|
FloatChannels[i]->SetDefault(DefaultTransformValues[i]);
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::PositionX;
|
|
bool bNegate = false;
|
|
const FControlRigChannelEnum ChannelEnum = (FControlRigChannelEnum)(ChannelEnumIndex + i);
|
|
if (const FControlToTransformMappings* ChannelMapping = ImportFBXControlRigSettings->ControlChannelMappings.FindByPredicate(
|
|
[ChannelEnum](const FControlToTransformMappings& Mapping) { return ChannelEnum == Mapping.ControlChannel; }))
|
|
{
|
|
bNegate = ChannelMapping->bNegate;
|
|
}
|
|
ImportTransformChannelToFloat(Transform[i], FloatChannels[i], FrameRate, bNegate, false, StartFrame, bNegate);
|
|
}
|
|
}
|
|
|
|
ChannelEnumIndex = INDEX_NONE;
|
|
|
|
if (DoubleChannels.Num() == 1)
|
|
{
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::Float;
|
|
}
|
|
else if (DoubleChannels.Num() == 2)
|
|
{
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::Vector2DX;
|
|
}
|
|
else if (DoubleChannels.Num() == 3)
|
|
{
|
|
if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Position)
|
|
{
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::PositionX;
|
|
}
|
|
else if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Rotator)
|
|
{
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::RotatorX;
|
|
}
|
|
else if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Scale)
|
|
{
|
|
ChannelEnumIndex = (int)FControlRigChannelEnum::ScaleX;
|
|
}
|
|
}
|
|
if (ChannelEnumIndex != INDEX_NONE)
|
|
{
|
|
for (int i = 0; i < DoubleChannels.Num(); i++)
|
|
{
|
|
const FControlRigChannelEnum ChannelEnum = (FControlRigChannelEnum)(ChannelEnumIndex + i);
|
|
SetChannelValue(DoubleChannels[i], nullptr, nullptr, nullptr, nullptr,
|
|
FrameRate, StartFrame, ChannelEnum, ImportFBXControlRigSettings, DefaultTransform,
|
|
Transform[0], Transform[1], Transform[2], Transform[3],
|
|
Transform[4], Transform[5], Transform[6], Transform[7], Transform[8]);
|
|
}
|
|
}
|
|
else if (DoubleChannels.Num() == 9 || DoubleChannels.Num() == 6)
|
|
{
|
|
for (int i = 0; i < DoubleChannels.Num(); i++)
|
|
{
|
|
DoubleChannels[i]->SetDefault(DefaultTransformValues[i]);
|
|
ImportTransformChannelToDouble(Transform[i], DoubleChannels[i], FrameRate, false, false, StartFrame);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
static FString GetNewString(const FString& InString, UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings)
|
|
{
|
|
FString NewString = InString;
|
|
int Index = INDEX_NONE;
|
|
|
|
if (ImportFBXControlRigSettings->bStripNamespace && InString.FindLastChar(':', Index))
|
|
{
|
|
NewString.RightChopInline(Index + 1);
|
|
}
|
|
for (const FControlFindReplaceString& FindReplace : ImportFBXControlRigSettings->FindAndReplaceStrings)
|
|
{
|
|
NewString.ReplaceInline(*FindReplace.Find, *FindReplace.Replace); //ignores tupe
|
|
}
|
|
return NewString;
|
|
}
|
|
|
|
static void PrepForInsertReplaceAnimation(bool bInsert, const FRigControlFBXNodeAndChannels& NodeAndChannel,
|
|
FFrameNumber FrameToInsertOrReplace, FFrameNumber StartFrame, FFrameNumber EndFrame)
|
|
{
|
|
|
|
TArray<FMovieSceneChannel*> Channels;
|
|
for (FMovieSceneDoubleChannel* DChannel : NodeAndChannel.DoubleChannels)
|
|
{
|
|
Channels.Add(DChannel);
|
|
}
|
|
for (FMovieSceneFloatChannel* FChannel : NodeAndChannel.FloatChannels)
|
|
{
|
|
Channels.Add(FChannel);
|
|
}
|
|
for (FMovieSceneBoolChannel* BChannel : NodeAndChannel.BoolChannels)
|
|
{
|
|
Channels.Add(BChannel);
|
|
}
|
|
for (FMovieSceneByteChannel* EChannel : NodeAndChannel.EnumChannels)
|
|
{
|
|
Channels.Add(EChannel);
|
|
}
|
|
for (FMovieSceneIntegerChannel* IChannel : NodeAndChannel.IntegerChannels)
|
|
{
|
|
Channels.Add(IChannel);
|
|
}
|
|
|
|
FFrameNumber Diff = EndFrame - StartFrame;
|
|
FrameToInsertOrReplace += StartFrame;
|
|
if (bInsert)
|
|
{
|
|
for (FMovieSceneChannel* Channel : Channels)
|
|
{
|
|
TArray<FFrameNumber> KeyTimes;
|
|
TArray<FKeyHandle> Handles;
|
|
Channel->GetKeys(TRange<FFrameNumber>(), &KeyTimes, &Handles);
|
|
for (int32 Index = 0; Index < KeyTimes.Num(); Index++)
|
|
{
|
|
FFrameNumber FrameNumber = KeyTimes[Index];
|
|
if (FrameNumber >= FrameToInsertOrReplace)
|
|
{
|
|
FrameNumber += Diff;
|
|
KeyTimes[Index] += Diff;
|
|
}
|
|
|
|
}
|
|
Channel->SetKeyTimes(Handles, KeyTimes);
|
|
}
|
|
}
|
|
else //we replace the animation by first deleting keys in the interval
|
|
{
|
|
for (FMovieSceneChannel* Channel : Channels)
|
|
{
|
|
TArray<FFrameNumber> KeyTimes;
|
|
TArray<FKeyHandle> Handles;
|
|
Channel->GetKeys(TRange<FFrameNumber>(), &KeyTimes, &Handles);
|
|
TArray<FKeyHandle> HandlesToDelete;
|
|
for (int32 Index = 0; Index < KeyTimes.Num(); Index++)
|
|
{
|
|
FFrameNumber FrameNumber = KeyTimes[Index];
|
|
if (FrameNumber >= FrameToInsertOrReplace && FrameNumber <= (FrameToInsertOrReplace + EndFrame))
|
|
{
|
|
HandlesToDelete.Add(Handles[Index]);
|
|
}
|
|
}
|
|
Channel->DeleteKeys(HandlesToDelete);
|
|
}
|
|
}
|
|
}
|
|
|
|
class SControlRigImportFBXSettings : public SCompoundWidget
|
|
{
|
|
SLATE_BEGIN_ARGS(SControlRigImportFBXSettings) {}
|
|
SLATE_ARGUMENT(FString, ImportFilename)
|
|
SLATE_END_ARGS()
|
|
~SControlRigImportFBXSettings()
|
|
{
|
|
if (NodeAndChannels != nullptr)
|
|
{
|
|
delete NodeAndChannels;
|
|
}
|
|
}
|
|
void Construct(const FArguments& InArgs, const TSharedRef<ISequencer>& InSequencer)
|
|
{
|
|
FPropertyEditorModule& PropertyEditor = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
DetailsViewArgs.bShowOptions = false;
|
|
DetailsViewArgs.bAllowSearch = false;
|
|
DetailsViewArgs.bShowPropertyMatrixButton = false;
|
|
DetailsViewArgs.bUpdatesFromSelection = false;
|
|
DetailsViewArgs.bLockable = false;
|
|
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
|
|
DetailsViewArgs.ViewIdentifier = "Import FBX Settings";
|
|
|
|
DetailView = PropertyEditor.CreateDetailView(DetailsViewArgs);
|
|
|
|
Sequencer = InSequencer;
|
|
|
|
DetailView->RegisterInstancedCustomPropertyTypeLayout("FrameNumber", FOnGetPropertyTypeCustomizationInstance::CreateSP(InSequencer, &ISequencer::MakeFrameNumberDetailsCustomization));
|
|
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
DetailView.ToSharedRef()
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SComboButton)
|
|
.HasDownArrow(true)
|
|
.OnGetMenuContent(this, &SControlRigImportFBXSettings::HandlePresetMenuContent)
|
|
.ButtonContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(NSLOCTEXT("MovieSceneTools", "ControlMappingPresets", "Control Mapping Presets"))
|
|
.ToolTipText(NSLOCTEXT("MovieSceneTools", "SetControlMappingFromAPreset", "Set Control Mappings From A Preset"))
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Right)
|
|
.Padding(5.f)
|
|
[
|
|
SNew(SButton)
|
|
.ContentPadding(FMargin(10, 5))
|
|
.Text(NSLOCTEXT("MovieSceneTools", "ImportFBXButtonText", "Import"))
|
|
.OnClicked(this, &SControlRigImportFBXSettings::OnImportFBXClicked)
|
|
]
|
|
|
|
];
|
|
|
|
ImportFilename = InArgs._ImportFilename;
|
|
NodeAndChannels = nullptr;
|
|
UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXControlRigSettings>();
|
|
DetailView->SetObject(ImportFBXSettings);
|
|
}
|
|
TSharedRef<SWidget> HandlePresetMenuContent()
|
|
{
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
NSLOCTEXT("MovieSceneTools", "DefaultControlMappings", "Default Control Mappings"),
|
|
NSLOCTEXT("MovieSceneTools", "DefaultControlMappings_Tooltip", "Use Default Control Mappings Preset"),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SControlRigImportFBXSettings::SetPresets,false)
|
|
),
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
NSLOCTEXT("MovieSceneTools", "MetaHumanControlMappings", "MetaHuman Control Mappings"),
|
|
NSLOCTEXT("MovieSceneTools", "MetaHumanControlMappings_Tooltip", "Use MetaHuman Control Mappings Preset"),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SControlRigImportFBXSettings::SetPresets,true)
|
|
),
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void SetNodeNames(const TArray<FString>& NodeNames)
|
|
{
|
|
UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXControlRigSettings>();
|
|
if (ImportFBXSettings)
|
|
{
|
|
ImportFBXSettings->ImportedNodeNames = NodeNames;
|
|
}
|
|
}
|
|
void SetFrameRate(const FString& InFrameRate)
|
|
{
|
|
UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXControlRigSettings>();
|
|
if (ImportFBXSettings)
|
|
{
|
|
ImportFBXSettings->ImportedFrameRate = InFrameRate;
|
|
}
|
|
}
|
|
void SetStartTime(FFrameNumber StartTime)
|
|
{
|
|
UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXControlRigSettings>();
|
|
if (ImportFBXSettings)
|
|
{
|
|
ImportFBXSettings->ImportedStartTime = StartTime;
|
|
ImportFBXSettings->StartTimeRange = StartTime;
|
|
}
|
|
}
|
|
void SetEndTime(FFrameNumber EndTime)
|
|
{
|
|
UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXControlRigSettings>();
|
|
if (ImportFBXSettings)
|
|
{
|
|
ImportFBXSettings->ImportedEndTime = EndTime;
|
|
ImportFBXSettings->EndTimeRange = EndTime;
|
|
}
|
|
}
|
|
void SetFileName(const FString& FileName)
|
|
{
|
|
UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXControlRigSettings>();
|
|
if (ImportFBXSettings)
|
|
{
|
|
ImportFBXSettings->ImportedFileName = FileName;
|
|
}
|
|
}
|
|
void SetNodeAndChannels(TArray<FRigControlFBXNodeAndChannels>* InNodeAndChannels)
|
|
{
|
|
NodeAndChannels = InNodeAndChannels;
|
|
}
|
|
|
|
private:
|
|
|
|
FReply OnImportFBXClicked()
|
|
{
|
|
if (!Sequencer.IsValid() || !NodeAndChannels)
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings = GetMutableDefault<UMovieSceneUserImportFBXControlRigSettings>();
|
|
|
|
TArray<FName> SelectedControlNames;
|
|
for (const FRigControlFBXNodeAndChannels& NodeAndChannel : *NodeAndChannels)
|
|
{
|
|
if (NodeAndChannel.MovieSceneTrack)
|
|
{
|
|
INodeAndChannelMappings* ChannelMapping = Cast<INodeAndChannelMappings>(NodeAndChannel.MovieSceneTrack);
|
|
if (ChannelMapping)
|
|
{
|
|
TArray<FName> LocalControls;
|
|
ChannelMapping->GetSelectedNodes(LocalControls);
|
|
SelectedControlNames.Append(LocalControls);
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool bValid = MovieSceneToolHelpers::ImportFBXIntoControlRigChannels(
|
|
Sequencer.Pin()->GetFocusedMovieSceneSequence()->GetMovieScene(), ImportFilename, ImportFBXControlRigSettings,
|
|
NodeAndChannels,SelectedControlNames, Sequencer.Pin()->GetFocusedTickResolution());
|
|
|
|
const TSharedPtr<SWindow> Window = FSlateApplication::Get().FindWidgetWindow(AsShared());
|
|
|
|
if (Window.IsValid())
|
|
{
|
|
Window->RequestDestroyWindow();
|
|
}
|
|
if (bValid)
|
|
{
|
|
Sequencer.Pin()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
|
|
}
|
|
return bValid ? FReply::Handled() : FReply::Unhandled();
|
|
}
|
|
|
|
void SetPresets(const bool bMetaHuman) const
|
|
{
|
|
UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings = GetMutableDefault<UMovieSceneUserImportFBXControlRigSettings>();
|
|
ImportFBXControlRigSettings->LoadControlMappingsFromPreset(bMetaHuman);
|
|
}
|
|
|
|
TSharedPtr<IDetailsView> DetailView;
|
|
FString ImportFilename;
|
|
TArray<FRigControlFBXNodeAndChannels>* NodeAndChannels = nullptr;
|
|
TWeakPtr<ISequencer> Sequencer;
|
|
};
|
|
|
|
class SControlRigExportFBXSettings : public SCompoundWidget
|
|
{
|
|
SLATE_BEGIN_ARGS(SControlRigExportFBXSettings) {}
|
|
SLATE_ARGUMENT(FString, ExportFilename)
|
|
SLATE_END_ARGS()
|
|
|
|
void Construct(const FArguments& InArgs, const TSharedRef<ISequencer>& InSequencer, TWeakObjectPtr<UMovieSceneTrack> InTrack)
|
|
{
|
|
Sequencer = InSequencer;
|
|
Track = InTrack;
|
|
|
|
UMovieSceneUserExportFBXControlRigSettings* ExportFBXSettings = GetMutableDefault<UMovieSceneUserExportFBXControlRigSettings>();
|
|
ExportFBXSettings->ExportFileName = InArgs._ExportFilename;
|
|
|
|
FPropertyEditorModule& PropertyEditor = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
DetailsViewArgs.bShowOptions = false;
|
|
DetailsViewArgs.bAllowSearch = false;
|
|
DetailsViewArgs.bShowPropertyMatrixButton = false;
|
|
DetailsViewArgs.bUpdatesFromSelection = false;
|
|
DetailsViewArgs.bLockable = false;
|
|
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
|
|
DetailsViewArgs.ViewIdentifier = "Export FBX Settings";
|
|
|
|
DetailView = PropertyEditor.CreateDetailView(DetailsViewArgs);
|
|
DetailView->SetObject(ExportFBXSettings);
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
DetailView.ToSharedRef()
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SComboButton)
|
|
.HasDownArrow(true)
|
|
.OnGetMenuContent(this, &SControlRigExportFBXSettings::HandlePresetMenuContent)
|
|
.ButtonContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(NSLOCTEXT("MovieSceneTools", "ControlMappingPresets", "Control Mapping Presets"))
|
|
.ToolTipText(NSLOCTEXT("MovieSceneTools", "SetControlMappingFromAPreset", "Set Control Mappings From A Preset"))
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Right)
|
|
.Padding(5.f)
|
|
[
|
|
SNew(SButton)
|
|
.ContentPadding(FMargin(10, 5))
|
|
.Text(NSLOCTEXT("MovieSceneTools", "ExportFBXButtonText", "Export"))
|
|
.OnClicked(this, &SControlRigExportFBXSettings::OnExportFBXClicked)
|
|
]
|
|
];
|
|
}
|
|
TSharedRef<SWidget> HandlePresetMenuContent()
|
|
{
|
|
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
NSLOCTEXT("MovieSceneTools", "EmptyControlMappings", "Empty Control Mappings"),
|
|
NSLOCTEXT("MovieSceneTools", "EmptyControlMappings_Tooltip",
|
|
"Do not use Control Mappings. Bool, Enum, Int and Float channels will be exported as attributes of the corresponding data type"),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SControlRigExportFBXSettings::ClearPresets)
|
|
),
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
NSLOCTEXT("MovieSceneTools", "DefaultControlMappings", "Default Control Mappings"),
|
|
NSLOCTEXT("MovieSceneTools", "DefaultControlMappings_Tooltip", "Use Default Control Mappings Preset"),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SControlRigExportFBXSettings::SetPresets, false)
|
|
),
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
NSLOCTEXT("MovieSceneTools", "MetaHumanControlMappings", "MetaHuman Control Mappings"),
|
|
NSLOCTEXT("MovieSceneTools", "MetaHumanControlMappings_Tooltip", "Use MetaHuman Control Mappings Preset"),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SControlRigExportFBXSettings::SetPresets, true)
|
|
),
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
private:
|
|
|
|
FReply OnExportFBXClicked()
|
|
{
|
|
if (!Sequencer.IsValid() || !Track.IsValid())
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
UMovieSceneUserExportFBXControlRigSettings* ExportFBXControlRigSettings = GetMutableDefault<UMovieSceneUserExportFBXControlRigSettings>();
|
|
|
|
TArray<FName> SelectedControlNames;
|
|
if (Track.IsValid())
|
|
{
|
|
if (INodeAndChannelMappings* ChannelMapping = Cast<INodeAndChannelMappings>(Track.Get()))
|
|
{
|
|
ChannelMapping->GetSelectedNodes(SelectedControlNames);
|
|
}
|
|
}
|
|
|
|
FMovieSceneSequenceTransform RootToLocalTransform;
|
|
if (Sequencer.IsValid())
|
|
{
|
|
RootToLocalTransform = Sequencer.Pin()->GetFocusedMovieSceneSequenceTransform();
|
|
}
|
|
|
|
const bool bValid = MovieSceneToolHelpers::ExportFBXFromControlRigChannels(
|
|
Track.Get()->GetSectionToKey(), ExportFBXControlRigSettings, SelectedControlNames, RootToLocalTransform);
|
|
|
|
if (bValid)
|
|
{
|
|
FNotificationInfo Info(NSLOCTEXT("MovieSceneTools", "ExportControlRigFBXSucceeded", "FBX Export Succeeded."));
|
|
Info.Hyperlink = FSimpleDelegate::CreateStatic([](FString InFilename) { FPlatformProcess::ExploreFolder(*InFilename); }, ExportFBXControlRigSettings->ExportFileName);
|
|
Info.HyperlinkText = FText::FromString(ExportFBXControlRigSettings->ExportFileName);
|
|
Info.ExpireDuration = 5.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Success);
|
|
}
|
|
else
|
|
{
|
|
FNotificationInfo Info(NSLOCTEXT("MovieSceneTools", "ExportControlRigFBXFailed", "FBX Export Failed."));
|
|
Info.ExpireDuration = 5.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail);
|
|
}
|
|
|
|
const TSharedPtr<SWindow> Window = FSlateApplication::Get().FindWidgetWindow(AsShared());
|
|
|
|
if (Window.IsValid())
|
|
{
|
|
Window->RequestDestroyWindow();
|
|
}
|
|
return bValid ? FReply::Handled() : FReply::Unhandled();
|
|
}
|
|
|
|
void SetPresets(const bool bMetaHuman) const
|
|
{
|
|
UMovieSceneUserExportFBXControlRigSettings* ExportFBXControlRigSettings = GetMutableDefault<UMovieSceneUserExportFBXControlRigSettings>();
|
|
ExportFBXControlRigSettings->LoadControlMappingsFromPreset(bMetaHuman);
|
|
}
|
|
|
|
void ClearPresets() const
|
|
{
|
|
UMovieSceneUserExportFBXControlRigSettings* ExportFBXControlRigSettings = GetMutableDefault<UMovieSceneUserExportFBXControlRigSettings>();
|
|
ExportFBXControlRigSettings->ControlChannelMappings.Empty();
|
|
}
|
|
|
|
TSharedPtr<IDetailsView> DetailView;
|
|
TWeakPtr<ISequencer> Sequencer;
|
|
TWeakObjectPtr<UMovieSceneTrack> Track;
|
|
};
|
|
|
|
bool MovieSceneToolHelpers::ImportFBXIntoControlRigChannels(UMovieScene* MovieScene,const FString& ImportFilename, UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings,
|
|
TArray<FRigControlFBXNodeAndChannels>* NodeAndChannels, const TArray<FName>& SelectedControlNames, FFrameRate FrameRate)
|
|
{
|
|
UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
|
|
bool bValid = true;
|
|
|
|
UnFbx::FBXImportOptions* ImportOptions = FbxImporter->GetImportOptions();
|
|
bool bOldbConvertScene = ImportOptions->bConvertScene;
|
|
bool bOldbConvertSceneUnit = ImportOptions->bConvertSceneUnit;
|
|
bool bOldbForceFrontXAxis = ImportOptions->bForceFrontXAxis;
|
|
float OldUniformScale = ImportOptions->ImportUniformScale;
|
|
EFBXAnimationLengthImportType OldAnimLengthType = ImportOptions->AnimationLengthImportType;
|
|
|
|
|
|
ImportOptions->bConvertScene = true;
|
|
ImportOptions->bConvertSceneUnit = ImportFBXControlRigSettings->bConvertSceneUnit;
|
|
ImportOptions->bForceFrontXAxis = ImportFBXControlRigSettings->bForceFrontXAxis;
|
|
ImportOptions->ImportUniformScale = ImportFBXControlRigSettings->ImportUniformScale;
|
|
ImportOptions->AnimationLengthImportType = FBXALIT_ExportedTime;
|
|
|
|
const FString FileExtension = FPaths::GetExtension(ImportFilename);
|
|
if (!FbxImporter->ImportFromFile(*ImportFilename, FileExtension, true))
|
|
{
|
|
// Log the error message and fail the import.
|
|
FbxImporter->ReleaseScene();
|
|
bValid = false;
|
|
}
|
|
else
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("MovieSceneTools", "ImportFBXControlRigTransaction", "Import FBX Onto Control Rig"));
|
|
|
|
UMovieSceneUserImportFBXSettings* CurrentImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXSettings>();
|
|
TArray<uint8> OriginalSettings;
|
|
FObjectWriter ObjWriter(CurrentImportFBXSettings, OriginalSettings);
|
|
|
|
CurrentImportFBXSettings->bMatchByNameOnly = false;
|
|
CurrentImportFBXSettings->bConvertSceneUnit = ImportFBXControlRigSettings->bConvertSceneUnit;
|
|
CurrentImportFBXSettings->bForceFrontXAxis = ImportFBXControlRigSettings->bForceFrontXAxis;
|
|
CurrentImportFBXSettings->ImportUniformScale = ImportFBXControlRigSettings->ImportUniformScale;
|
|
CurrentImportFBXSettings->bCreateCameras = false;
|
|
CurrentImportFBXSettings->bReduceKeys = false;
|
|
CurrentImportFBXSettings->ReduceKeysTolerance = 0.01f;
|
|
|
|
UnFbx::FFbxCurvesAPI CurveAPI;
|
|
FbxImporter->PopulateAnimatedCurveData(CurveAPI);
|
|
TArray<FString> AllNodeNames;
|
|
CurveAPI.GetAllNodeNameArray(AllNodeNames);
|
|
|
|
//if matching selected remove out the non-selected
|
|
if (ImportFBXControlRigSettings->bImportOntoSelectedControls)
|
|
{
|
|
NodeAndChannels->RemoveAll([SelectedControlNames](const FRigControlFBXNodeAndChannels& A)
|
|
{ return !SelectedControlNames.Contains(A.ControlName); }
|
|
);
|
|
}
|
|
|
|
FFrameNumber FrameToInsertOrReplace = ImportFBXControlRigSettings->TimeToInsertOrReplaceAnimation;
|
|
|
|
FFrameNumber StartFrame = ImportFBXControlRigSettings->StartTimeRange;
|
|
FFrameNumber EndFrame = ImportFBXControlRigSettings->EndTimeRange;
|
|
|
|
// We'll be looking for timecode properties on all of the nodes in the FBX being
|
|
// considered for import, and the first one found will be used to populate the
|
|
// timecode source of all modified movie scene sections.
|
|
FName TCHourAttrName(TEXT("TCHour"));
|
|
FName TCMinuteAttrName(TEXT("TCMinute"));
|
|
FName TCSecondAttrName(TEXT("TCSecond"));
|
|
FName TCFrameAttrName(TEXT("TCFrame"));
|
|
|
|
if (const UAnimationSettings* AnimationSettings = UAnimationSettings::Get())
|
|
{
|
|
TCHourAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.HourAttributeName;
|
|
TCMinuteAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.MinuteAttributeName;
|
|
TCSecondAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.SecondAttributeName;
|
|
TCFrameAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.FrameAttributeName;
|
|
}
|
|
|
|
const TArray<FString> TimecodePropertyNames = {
|
|
TCHourAttrName.ToString(),
|
|
TCMinuteAttrName.ToString(),
|
|
TCSecondAttrName.ToString(),
|
|
TCFrameAttrName.ToString()
|
|
};
|
|
|
|
FTimecode Timecode;
|
|
bool bFoundTimecode = false;
|
|
|
|
TSet<UMovieSceneSection*> AllModifiedSections;
|
|
|
|
for (const FString& NodeName : AllNodeNames)
|
|
{
|
|
const FString NewNodeName = GetNewString(*NodeName, ImportFBXControlRigSettings);
|
|
|
|
TSet<UMovieSceneSection*> ModifiedSections;
|
|
for (FRigControlFBXNodeAndChannels& NodeAndChannel : *NodeAndChannels)
|
|
{
|
|
if (NodeAndChannel.NodeName.Equals(NewNodeName, ESearchCase::IgnoreCase))
|
|
{
|
|
if (NodeAndChannel.MovieSceneTrack)
|
|
{
|
|
UMovieSceneSection* SectionToModify = NodeAndChannel.MovieSceneTrack->GetSectionToKey();
|
|
if (!SectionToModify && NodeAndChannel.MovieSceneTrack->GetAllSections().Num() > 0)
|
|
{
|
|
SectionToModify = NodeAndChannel.MovieSceneTrack->GetAllSections()[0];
|
|
}
|
|
|
|
if (SectionToModify && !ModifiedSections.Contains(SectionToModify))
|
|
{
|
|
SectionToModify->SetFlags(RF_Transactional);
|
|
SectionToModify->Modify();
|
|
ModifiedSections.Add(SectionToModify);
|
|
AllModifiedSections.Add(SectionToModify);
|
|
}
|
|
}
|
|
|
|
PrepForInsertReplaceAnimation(ImportFBXControlRigSettings->bInsertAnimation, NodeAndChannel,
|
|
FrameToInsertOrReplace,
|
|
StartFrame, EndFrame);
|
|
|
|
FFBXControlRigTypeProxyEnum ControlType = NodeAndChannel.ControlType;
|
|
bool bTryImportNonTransformCurves = ControlType == FFBXControlRigTypeProxyEnum::Bool || ControlType == FFBXControlRigTypeProxyEnum::Float || ControlType == FFBXControlRigTypeProxyEnum::Integer;
|
|
|
|
// If the node channels could not be imported from custom FBX attribute curves, try importing it them from the FBX transform channels, using the corresponding mapping
|
|
if (!bTryImportNonTransformCurves || !ImportFBXNonTransformCurvesToChannels(
|
|
NodeName, CurrentImportFBXSettings, ImportFBXControlRigSettings, FrameToInsertOrReplace,
|
|
FrameRate, NodeAndChannel, CurveAPI))
|
|
{
|
|
ImportFBXTransformToChannels(NodeName, CurrentImportFBXSettings, ImportFBXControlRigSettings, FrameToInsertOrReplace, FrameRate, NodeAndChannel, CurveAPI);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We consider *all* nodes in the FBX when looking for authored timecode properties,
|
|
// not just the ones that map to a particular node with channels.
|
|
if (!bFoundTimecode)
|
|
{
|
|
TArray<FString> AnimatedPropertyNames;
|
|
CurveAPI.GetNodeAnimatedPropertyNameArray(NodeName, AnimatedPropertyNames);
|
|
for (const FString& AnimatedPropertyName : AnimatedPropertyNames)
|
|
{
|
|
if (!TimecodePropertyNames.Contains(AnimatedPropertyName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bFoundTimecode = true;
|
|
|
|
const int32 ChannelIndex = 0;
|
|
const int32 CompositeIndex = 0;
|
|
FRichCurve PropertyCurve;
|
|
const bool bNegative = false;
|
|
CurveAPI.GetCurveDataForSequencer(NodeName, AnimatedPropertyName, ChannelIndex, CompositeIndex, PropertyCurve, bNegative);
|
|
|
|
const float EvalTime = 0.0f;
|
|
const float DefaultValue = 0.0f;
|
|
const float Value = PropertyCurve.Eval(EvalTime, DefaultValue);
|
|
const int32 IntValue = FMath::TruncToInt(Value);
|
|
|
|
const FName AnimatedPropertyNameAsFName(AnimatedPropertyName);
|
|
|
|
if (AnimatedPropertyNameAsFName.IsEqual(TCHourAttrName))
|
|
{
|
|
Timecode.Hours = IntValue;
|
|
}
|
|
else if (AnimatedPropertyNameAsFName.IsEqual(TCMinuteAttrName))
|
|
{
|
|
Timecode.Minutes = IntValue;
|
|
}
|
|
else if (AnimatedPropertyNameAsFName.IsEqual(TCSecondAttrName))
|
|
{
|
|
Timecode.Seconds = IntValue;
|
|
}
|
|
else if (AnimatedPropertyNameAsFName.IsEqual(TCFrameAttrName))
|
|
{
|
|
Timecode.Frames = IntValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bFoundTimecode)
|
|
{
|
|
for (UMovieSceneSection* Section : AllModifiedSections)
|
|
{
|
|
Section->TimecodeSource = FMovieSceneTimecodeSource(Timecode);
|
|
}
|
|
}
|
|
|
|
// restore
|
|
FObjectReader ObjReader(GetMutableDefault<UMovieSceneUserImportFBXSettings>(), OriginalSettings);
|
|
FbxImporter->ReleaseScene();
|
|
}
|
|
|
|
ImportOptions->AnimationLengthImportType = OldAnimLengthType;
|
|
ImportOptions->bConvertScene = bOldbConvertScene;
|
|
ImportOptions->bConvertSceneUnit = bOldbConvertSceneUnit;
|
|
ImportOptions->bForceFrontXAxis = bOldbForceFrontXAxis;
|
|
ImportOptions->ImportUniformScale = OldUniformScale;;
|
|
return bValid;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ExportFBXFromControlRigChannels(const UMovieSceneSection* Section,
|
|
const UMovieSceneUserExportFBXControlRigSettings* ExportFBXControlRigSettings, const TArray<FName>& SelectedControlNames,
|
|
const FMovieSceneSequenceTransform& RootToLocalTransform)
|
|
{
|
|
if (!Section)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance();
|
|
|
|
UFbxExportOption* ExportOptions = Exporter->GetExportOptions();
|
|
ExportOptions->FbxExportCompatibility = ExportFBXControlRigSettings->FbxExportCompatibility;
|
|
ExportOptions->bASCII = ExportFBXControlRigSettings->bASCII;
|
|
ExportOptions->bForceFrontXAxis = ExportFBXControlRigSettings->bForceFrontXAxis;
|
|
ExportOptions->bExportLocalTime = ExportFBXControlRigSettings->bExportLocalTime;
|
|
|
|
Exporter->CreateDocument();
|
|
Exporter->SetTransformBaking(true);
|
|
Exporter->SetKeepHierarchy(true);
|
|
|
|
TArray<FControlRigFbxNodeMapping> ChannelsMapping;
|
|
|
|
// Map to make it easier to convert from Control Rig Channel Enum to Control Rig Type Proxy + Transform Channel Index
|
|
const TMap<FControlRigChannelEnum, TPair<FFBXControlRigTypeProxyEnum, uint8>> ProxyMapping = {
|
|
{ FControlRigChannelEnum::Vector2DX, { FFBXControlRigTypeProxyEnum::Vector2D, 0 } },
|
|
{ FControlRigChannelEnum::Vector2DY, { FFBXControlRigTypeProxyEnum::Vector2D, 1 } },
|
|
{ FControlRigChannelEnum::PositionX, { FFBXControlRigTypeProxyEnum::Position, 0 } },
|
|
{ FControlRigChannelEnum::PositionY, { FFBXControlRigTypeProxyEnum::Position, 1 } },
|
|
{ FControlRigChannelEnum::PositionZ, { FFBXControlRigTypeProxyEnum::Position, 2 } },
|
|
{ FControlRigChannelEnum::RotatorX, { FFBXControlRigTypeProxyEnum::Rotator, 0 } },
|
|
{ FControlRigChannelEnum::RotatorY, { FFBXControlRigTypeProxyEnum::Rotator, 1 } },
|
|
{ FControlRigChannelEnum::RotatorZ, { FFBXControlRigTypeProxyEnum::Rotator, 2 } },
|
|
{ FControlRigChannelEnum::ScaleX, { FFBXControlRigTypeProxyEnum::Scale, 0 } },
|
|
{ FControlRigChannelEnum::ScaleY, { FFBXControlRigTypeProxyEnum::Scale, 1 } },
|
|
{ FControlRigChannelEnum::ScaleZ, { FFBXControlRigTypeProxyEnum::Scale, 2 } }
|
|
};
|
|
|
|
for (const FControlToTransformMappings& ControlMapping : ExportFBXControlRigSettings->ControlChannelMappings)
|
|
{
|
|
FControlRigFbxNodeMapping ChannelMapping;
|
|
|
|
if (ControlMapping.ControlChannel == FControlRigChannelEnum::Bool)
|
|
{
|
|
ChannelMapping.ChannelType = FMovieSceneBoolChannel::StaticStruct()->GetFName();
|
|
ChannelMapping.ControlType = FFBXControlRigTypeProxyEnum::Bool;
|
|
}
|
|
else if (ControlMapping.ControlChannel == FControlRigChannelEnum::Enum)
|
|
{
|
|
ChannelMapping.ChannelType = FMovieSceneByteChannel::StaticStruct()->GetFName();
|
|
ChannelMapping.ControlType = FFBXControlRigTypeProxyEnum::Integer;
|
|
}
|
|
else if (ControlMapping.ControlChannel == FControlRigChannelEnum::Integer)
|
|
{
|
|
ChannelMapping.ChannelType = FMovieSceneIntegerChannel::StaticStruct()->GetFName();
|
|
ChannelMapping.ControlType = FFBXControlRigTypeProxyEnum::Integer;
|
|
}
|
|
else
|
|
{
|
|
ChannelMapping.ChannelType = FMovieSceneFloatChannel::StaticStruct()->GetFName();
|
|
|
|
// Convert Control Channel Type to Control Type and Transform Component Index (0-2)
|
|
if (const TPair<FFBXControlRigTypeProxyEnum, uint8>* Mapping = ProxyMapping.Find(ControlMapping.ControlChannel))
|
|
{
|
|
ChannelMapping.ControlType = Mapping->Key;
|
|
ChannelMapping.ChannelAttrIndex = Mapping->Value;
|
|
}
|
|
else
|
|
{
|
|
ChannelMapping.ControlType = FFBXControlRigTypeProxyEnum::Float;
|
|
}
|
|
}
|
|
|
|
// Convert FBXChannel Type to Transform Component Index (0-8)
|
|
ChannelMapping.FbxAttrIndex = (uint8)ControlMapping.FBXChannel;
|
|
|
|
ChannelMapping.bNegate = ControlMapping.bNegate;
|
|
ChannelsMapping.Add(ChannelMapping);
|
|
}
|
|
|
|
// Filter only selected controls if required
|
|
TArray<FName> ControlsFilter;
|
|
if (ExportFBXControlRigSettings->bExportOnlySelectedControls)
|
|
{
|
|
ControlsFilter = SelectedControlNames;
|
|
}
|
|
|
|
Exporter->ExportControlRigSection(Section, ChannelsMapping, ControlsFilter, RootToLocalTransform);
|
|
|
|
// Save to disk
|
|
Exporter->WriteToFile(*ExportFBXControlRigSettings->ExportFileName);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ImportFBXIntoControlRigChannelsWithDialog(const TSharedRef<ISequencer>& InSequencer,TArray<FRigControlFBXNodeAndChannels>* NodeAndChannels)
|
|
{
|
|
TArray<FString> OpenFilenames;
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bOpen = false;
|
|
if (DesktopPlatform)
|
|
{
|
|
FString ExtensionStr;
|
|
ExtensionStr += TEXT("FBX (*.fbx)|*.fbx|");
|
|
|
|
bOpen = DesktopPlatform->OpenFileDialog(
|
|
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
|
|
NSLOCTEXT("MovieSceneTools", "ImportFBX", "Import FBX from...").ToString(),
|
|
FEditorDirectories::Get().GetLastDirectory(ELastDirectory::FBX),
|
|
TEXT(""),
|
|
*ExtensionStr,
|
|
EFileDialogFlags::None,
|
|
OpenFilenames
|
|
);
|
|
}
|
|
if (!bOpen)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!OpenFilenames.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FText TitleText = NSLOCTEXT("MovieSceneTools", "ImportFBXTitleOnToControlRig", "Import FBX Onto Control Rig");
|
|
|
|
// Create the window to choose our options
|
|
TSharedRef<SWindow> Window = SNew(SWindow)
|
|
.Title(TitleText)
|
|
.HasCloseButton(true)
|
|
.SizingRule(ESizingRule::UserSized)
|
|
.ClientSize(FVector2D(500.f, 700.f))
|
|
.AutoCenter(EAutoCenter::PreferredWorkArea)
|
|
.SupportsMinimize(false);
|
|
|
|
TSharedRef<SControlRigImportFBXSettings> DialogWidget = SNew(SControlRigImportFBXSettings, InSequencer)
|
|
.ImportFilename(OpenFilenames[0]);
|
|
|
|
|
|
UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
UnFbx::FBXImportOptions* ImportOptions = FbxImporter->GetImportOptions();
|
|
|
|
EFBXAnimationLengthImportType AnimLengthType = ImportOptions->AnimationLengthImportType;
|
|
ImportOptions->AnimationLengthImportType = FBXALIT_ExportedTime;
|
|
const FString FileExtension = FPaths::GetExtension(OpenFilenames[0]);
|
|
if (!FbxImporter->ImportFromFile(*OpenFilenames[0], FileExtension, true))
|
|
{
|
|
ImportOptions->AnimationLengthImportType = AnimLengthType;
|
|
if (NodeAndChannels)
|
|
{
|
|
delete NodeAndChannels;
|
|
}
|
|
FbxImporter->ReleaseScene();
|
|
return false;
|
|
}
|
|
UnFbx::FFbxCurvesAPI CurveAPI;
|
|
FbxImporter->PopulateAnimatedCurveData(CurveAPI);
|
|
TArray<FString> AllNodeNames;
|
|
CurveAPI.GetAllNodeNameArray(AllNodeNames);
|
|
FbxAnimStack* AnimStack = FbxImporter->Scene->GetMember<FbxAnimStack>(0);
|
|
|
|
FbxTimeSpan TimeSpan = FbxImporter->GetAnimationTimeSpan(FbxImporter->Scene->GetRootNode(), AnimStack);
|
|
ImportOptions->AnimationLengthImportType = AnimLengthType;
|
|
FbxImporter->ReleaseScene();
|
|
DialogWidget->SetFileName(OpenFilenames[0]);
|
|
FString FrameRateStr = FString::Printf(TEXT("%.2f"), FbxImporter->GetOriginalFbxFramerate());
|
|
|
|
DialogWidget->SetFrameRate(FrameRateStr);
|
|
FFrameRate FrameRate = InSequencer->GetFocusedTickResolution();
|
|
FFrameNumber StartTime = FrameRate.AsFrameNumber(TimeSpan.GetStart().GetSecondDouble());
|
|
FFrameNumber EndTime = FrameRate.AsFrameNumber(TimeSpan.GetStop().GetSecondDouble());
|
|
DialogWidget->SetStartTime(StartTime);
|
|
DialogWidget->SetEndTime(EndTime);
|
|
DialogWidget->SetNodeNames(AllNodeNames);
|
|
DialogWidget->SetNodeAndChannels(NodeAndChannels);
|
|
Window->SetContent(DialogWidget);
|
|
|
|
FSlateApplication::Get().AddWindow(Window);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ExportFBXFromControlRigChannelsWithDialog(const TSharedRef<ISequencer>& InSequencer, UMovieSceneTrack* Track)
|
|
{
|
|
FString ExportFilename;
|
|
|
|
if (IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get())
|
|
{
|
|
TArray<FString> SavedFiles;
|
|
|
|
const bool bFilePicked = DesktopPlatform->SaveFileDialog(
|
|
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
|
|
NSLOCTEXT("MovieSceneTools", "ExportControlRigFBX", "Export Control Rig FBX" ).ToString(),
|
|
*(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::FBX)),
|
|
TEXT(""),
|
|
TEXT("FBX document|*.fbx"),
|
|
EFileDialogFlags::None,
|
|
SavedFiles);
|
|
|
|
if (!bFilePicked || SavedFiles.IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
ExportFilename = SavedFiles[0];
|
|
FEditorDirectories::Get().SetLastDirectory(ELastDirectory::FBX, FPaths::GetPath(ExportFilename));
|
|
}
|
|
|
|
const FText TitleText = NSLOCTEXT("MovieSceneTools", "ExportFBXFromControlRigTitle", "Export FBX from Control Rig");
|
|
|
|
// Create the window to choose our options
|
|
const TSharedRef<SWindow> Window = SNew(SWindow)
|
|
.Title(TitleText)
|
|
.HasCloseButton(true)
|
|
.SizingRule(ESizingRule::UserSized)
|
|
.ClientSize(FVector2D(450.0f, 300.0f))
|
|
.AutoCenter(EAutoCenter::PreferredWorkArea)
|
|
.SupportsMinimize(false);
|
|
|
|
const TSharedRef<SControlRigExportFBXSettings> DialogWidget = SNew(SControlRigExportFBXSettings, InSequencer, Track)
|
|
.ExportFilename(ExportFilename);
|
|
|
|
Window->SetContent(DialogWidget);
|
|
|
|
FSlateApplication::Get().AddWindow(Window);
|
|
|
|
return true;
|
|
}
|
|
|
|
double UnwindKeyValue(const double OldValue, double NewValue)
|
|
{
|
|
while (NewValue - OldValue > 180.0f)
|
|
{
|
|
NewValue -= 360.0f;
|
|
}
|
|
while (NewValue - OldValue < -180.0f)
|
|
{
|
|
NewValue += 360.0f;
|
|
}
|
|
return NewValue;
|
|
}
|
|
|
|
void AddCorrectedKey(const FMovieSceneDoubleValue& CorrectedValue, const FFrameTime Time, TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData)
|
|
{
|
|
const int32 KeyIndex = ChannelData.FindKey(Time.RoundToFrame());
|
|
|
|
if (KeyIndex != INDEX_NONE)
|
|
{
|
|
FMovieSceneDoubleValue& KeyValue = ChannelData.GetValues()[KeyIndex];
|
|
|
|
KeyValue.Value = CorrectedValue.Value;
|
|
// Tangents need to be set to auto, so they can be corrected later, as their imported values may no longer be valid after correcting the transform
|
|
if (KeyValue.TangentMode != RCTM_Auto && KeyValue.TangentMode != RCTM_SmartAuto)
|
|
{
|
|
KeyValue.TangentMode = RCTM_Auto;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MovieSceneToolHelpers::SetOrAddKey(ChannelData, Time.RoundToFrame(), CorrectedValue);
|
|
}
|
|
}
|
|
|
|
void CorrectForTransformOrigin(IMovieScenePlayer* Player, UMovieScene3DTransformSection* TransformSection, ISequencer* Sequencer)
|
|
{
|
|
FMovieSceneChannelProxy& SectionChannelProxy = TransformSection->GetChannelProxy();
|
|
TMovieSceneChannelHandle<FMovieSceneDoubleChannel> DoubleChannels[] = {
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.Z"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.Z")
|
|
};
|
|
|
|
FFrameRate FrameRate = TransformSection->GetTypedOuter<UMovieScene>()->GetTickResolution();
|
|
|
|
// If importing from the Sequencer editor, retrieve the focused subsequence hierarchy, in order to get the subsequence transform origin overrides.
|
|
// If not importing from the Sequencer editor, assume the transform is being imported onto the root sequence.
|
|
TArray<FMovieSceneSequenceID> SubsequenceHierarchy = Sequencer ? Sequencer->GetSubSequenceHierarchy() : TArray { MovieSceneSequenceID::Root };
|
|
|
|
UE::MovieScene::FSystemInterrogator Interrogator;
|
|
|
|
const FMovieSceneRootEvaluationTemplateInstance& RootInstance = Player->GetEvaluationTemplate();
|
|
const FMovieSceneSequenceHierarchy* Hierarchy = RootInstance.GetCompiledDataManager()->FindHierarchy(RootInstance.GetCompiledDataID());
|
|
|
|
// Sub tracks can only be interrogated if the Sequencer and Hierarchy are valid (i.e. the import is triggered from the editor),
|
|
// but the instance data can still be interrogated if they aren't, so this step can be safely skipped when importing from scripting.
|
|
if (Sequencer && Hierarchy)
|
|
{
|
|
Interrogator.SetHierarchy(const_cast<FMovieSceneSequenceHierarchy*>(Hierarchy));
|
|
|
|
FMovieSceneSequenceID OwningSequenceID = MovieSceneSequenceID::Root;
|
|
for (const FMovieSceneSequenceID SubSequenceID : SubsequenceHierarchy)
|
|
{
|
|
if (const UMovieSceneSubSection* SubSection = Sequencer->FindSubSection(SubSequenceID))
|
|
{
|
|
if (UMovieSceneTrack* SubTrack = Cast<UMovieSceneTrack>(SubSection->GetOuter()))
|
|
{
|
|
Interrogator.ImportTrack(SubTrack, UE::MovieScene::FInterrogationChannel::FromIndex(SubSection->GetRowIndex()), OwningSequenceID);
|
|
}
|
|
}
|
|
OwningSequenceID = SubSequenceID;
|
|
}
|
|
}
|
|
|
|
TSet<FFrameNumber> Times;
|
|
|
|
for (int32 ChannelIndex = 0; ChannelIndex < 6; ++ChannelIndex)
|
|
{
|
|
TMovieSceneChannelHandle<FMovieSceneDoubleChannel> DoubleChannel = DoubleChannels[ChannelIndex];
|
|
|
|
for (FFrameNumber Time : DoubleChannel.Get()->GetTimes())
|
|
{
|
|
Times.Add(Time);
|
|
}
|
|
}
|
|
|
|
TArray<FFrameNumber> TimesElements = Times.Array();
|
|
TimesElements.Sort();
|
|
|
|
for (FFrameNumber Time : TimesElements)
|
|
{
|
|
Interrogator.AddInterrogation(Time);
|
|
}
|
|
|
|
const IMovieScenePlaybackClient* Client = Player->GetPlaybackClient();
|
|
const UObject* InstanceData = Client ? Client->GetInstanceData() : nullptr;
|
|
|
|
TArray<FTransform> TransformOrigins;
|
|
TArray<FTransform> CorrectedTransforms;
|
|
|
|
Interrogator.Update();
|
|
Interrogator.QueryTransformOrigins(TransformOrigins, SubsequenceHierarchy, InstanceData);
|
|
|
|
// Gather the corrected transforms
|
|
for (int32 TransformOriginIndex = 0; TransformOriginIndex < TransformOrigins.Num(); ++TransformOriginIndex)
|
|
{
|
|
FFrameTime Time = TimesElements[TransformOriginIndex];
|
|
FTransform TransformOrigin = TransformOrigins[TransformOriginIndex];
|
|
|
|
double LocationX = 0.0;
|
|
double LocationY = 0.0;
|
|
double LocationZ = 0.0;
|
|
|
|
double RotationX = 0.0;
|
|
double RotationY = 0.0;
|
|
double RotationZ = 0.0;
|
|
|
|
if (DoubleChannels[0].Get())
|
|
{
|
|
DoubleChannels[0].Get()->Evaluate(Time, LocationX);
|
|
}
|
|
|
|
if (DoubleChannels[1].Get())
|
|
{
|
|
DoubleChannels[1].Get()->Evaluate(Time, LocationY);
|
|
}
|
|
|
|
if (DoubleChannels[2].Get())
|
|
{
|
|
DoubleChannels[2].Get()->Evaluate(Time, LocationZ);
|
|
}
|
|
|
|
if (DoubleChannels[3].Get())
|
|
{
|
|
DoubleChannels[3].Get()->Evaluate(Time, RotationX);
|
|
}
|
|
|
|
if (DoubleChannels[4].Get())
|
|
{
|
|
DoubleChannels[4].Get()->Evaluate(Time, RotationY);
|
|
}
|
|
|
|
if (DoubleChannels[5].Get())
|
|
{
|
|
DoubleChannels[5].Get()->Evaluate(Time, RotationZ);
|
|
}
|
|
|
|
FVector EvaluatedLocation = FVector(LocationX, LocationY, LocationZ);
|
|
FRotator EvaluatedRotation = FRotator(RotationY, RotationZ, RotationX);
|
|
|
|
FTransform EvaluatedTransform = FTransform(EvaluatedRotation, EvaluatedLocation);
|
|
|
|
EvaluatedTransform *= TransformOrigin.Inverse();
|
|
|
|
CorrectedTransforms.Add(EvaluatedTransform);
|
|
}
|
|
|
|
TOptional<double> PreviousRotationX;
|
|
TOptional<double> PreviousRotationY;
|
|
TOptional<double> PreviousRotationZ;
|
|
|
|
// Apply the correct transforms. This has to be done after gathering, so the corrected transforms don't interfere with evaluation.
|
|
for (int32 CorrectedTransformIndex = 0; CorrectedTransformIndex < CorrectedTransforms.Num(); ++CorrectedTransformIndex)
|
|
{
|
|
FTransform CorrectedTransform = CorrectedTransforms[CorrectedTransformIndex];
|
|
FFrameTime Time = TimesElements[CorrectedTransformIndex];
|
|
|
|
if (DoubleChannels[0].Get())
|
|
{
|
|
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = DoubleChannels[0].Get()->GetData();
|
|
AddCorrectedKey(FMovieSceneDoubleValue(CorrectedTransform.GetTranslation().X), Time, ChannelData);
|
|
}
|
|
|
|
if (DoubleChannels[1].Get())
|
|
{
|
|
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = DoubleChannels[1].Get()->GetData();
|
|
AddCorrectedKey(FMovieSceneDoubleValue(CorrectedTransform.GetTranslation().Y), Time, ChannelData);
|
|
}
|
|
|
|
if (DoubleChannels[2].Get())
|
|
{
|
|
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = DoubleChannels[2].Get()->GetData();
|
|
AddCorrectedKey(FMovieSceneDoubleValue(CorrectedTransform.GetTranslation().Z), Time, ChannelData);
|
|
}
|
|
|
|
if (DoubleChannels[3].Get())
|
|
{
|
|
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = DoubleChannels[3].Get()->GetData();
|
|
FMovieSceneDoubleValue CorrectedValue = FMovieSceneDoubleValue(CorrectedTransform.GetRotation().Rotator().Roll);
|
|
|
|
if (PreviousRotationX.IsSet())
|
|
{
|
|
CorrectedValue.Value = UnwindKeyValue(PreviousRotationX.GetValue(), CorrectedValue.Value);
|
|
}
|
|
|
|
PreviousRotationX = CorrectedValue.Value;
|
|
|
|
AddCorrectedKey(CorrectedValue, Time, ChannelData);
|
|
}
|
|
|
|
if (DoubleChannels[4].Get())
|
|
{
|
|
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = DoubleChannels[4].Get()->GetData();
|
|
FMovieSceneDoubleValue CorrectedValue = FMovieSceneDoubleValue(CorrectedTransform.GetRotation().Rotator().Pitch);
|
|
|
|
if (PreviousRotationY.IsSet())
|
|
{
|
|
CorrectedValue.Value = UnwindKeyValue(PreviousRotationY.GetValue(), CorrectedValue.Value);
|
|
}
|
|
|
|
PreviousRotationY = CorrectedValue.Value;
|
|
|
|
AddCorrectedKey(CorrectedValue, Time, ChannelData);
|
|
}
|
|
|
|
if (DoubleChannels[5].Get())
|
|
{
|
|
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = DoubleChannels[5].Get()->GetData();
|
|
FMovieSceneDoubleValue CorrectedValue = FMovieSceneDoubleValue(CorrectedTransform.GetRotation().Rotator().Yaw);
|
|
|
|
if (PreviousRotationZ.IsSet())
|
|
{
|
|
CorrectedValue.Value = UnwindKeyValue(PreviousRotationZ.GetValue(), CorrectedValue.Value);
|
|
}
|
|
|
|
PreviousRotationZ = CorrectedValue.Value;
|
|
|
|
AddCorrectedKey(CorrectedValue, Time, ChannelData);
|
|
}
|
|
}
|
|
|
|
for (TMovieSceneChannelHandle<FMovieSceneDoubleChannel> DoubleChannel : DoubleChannels)
|
|
{
|
|
const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault<UMovieSceneUserImportFBXSettings>();
|
|
if (ImportFBXSettings->bReduceKeys)
|
|
{
|
|
FKeyDataOptimizationParams Params;
|
|
Params.Tolerance = ImportFBXSettings->ReduceKeysTolerance;
|
|
Params.DisplayRate = FrameRate;
|
|
DoubleChannel.Get()->Optimize(Params);
|
|
}
|
|
DoubleChannel.Get()->AutoSetTangents();
|
|
}
|
|
}
|
|
|
|
bool ImportFBXTransform(FString NodeName, FGuid ObjectBinding, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, ISequencer* Sequencer)
|
|
{
|
|
UMovieScene* MovieScene = InSequence->GetMovieScene();
|
|
|
|
const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault<UMovieSceneUserImportFBXSettings>();
|
|
|
|
// Look for transforms explicitly
|
|
FRichCurve Translation[3];
|
|
FRichCurve EulerRotation[3];
|
|
FRichCurve Scale[3];
|
|
FTransform DefaultTransform;
|
|
const bool bUseSequencerCurve = true;
|
|
CurveAPI.GetConvertedTransformCurveData(NodeName, Translation[0], Translation[1], Translation[2], EulerRotation[0], EulerRotation[1], EulerRotation[2], Scale[0], Scale[1], Scale[2], DefaultTransform, bUseSequencerCurve, ImportFBXSettings->ImportUniformScale);
|
|
|
|
UMovieScene3DTransformTrack* TransformTrack = MovieScene->FindTrack<UMovieScene3DTransformTrack>(ObjectBinding);
|
|
if (!TransformTrack)
|
|
{
|
|
MovieScene->Modify();
|
|
TransformTrack = MovieScene->AddTrack<UMovieScene3DTransformTrack>(ObjectBinding);
|
|
}
|
|
TransformTrack->Modify();
|
|
|
|
bool bSectionAdded = false;
|
|
UMovieScene3DTransformSection* TransformSection = Cast<UMovieScene3DTransformSection>(TransformTrack->FindSection(0));
|
|
if (TransformSection && !ImportFBXSettings->bReplaceTransformTrack)
|
|
{
|
|
TransformSection = Cast<UMovieScene3DTransformSection>(TransformTrack->CreateNewSection());
|
|
TransformSection->SetRowIndex(TransformTrack->GetMaxRowIndex()+1);
|
|
TransformTrack->AddSection(*TransformSection);
|
|
bSectionAdded = true;
|
|
}
|
|
else
|
|
{
|
|
TransformSection = Cast<UMovieScene3DTransformSection>(TransformTrack->FindOrAddSection(0, bSectionAdded));
|
|
}
|
|
|
|
if (!TransformSection)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TransformSection->Modify();
|
|
|
|
FFrameRate FrameRate = TransformSection->GetTypedOuter<UMovieScene>()->GetTickResolution();
|
|
|
|
if (bSectionAdded)
|
|
{
|
|
TransformSection->SetRange(TRange<FFrameNumber>::All());
|
|
}
|
|
|
|
FVector Location = DefaultTransform.GetLocation(), Rotation = DefaultTransform.GetRotation().Euler(), Scale3D = DefaultTransform.GetScale3D();
|
|
|
|
FMovieSceneChannelProxy& SectionChannelProxy = TransformSection->GetChannelProxy();
|
|
TMovieSceneChannelHandle<FMovieSceneDoubleChannel> DoubleChannels[] = {
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.Z"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.Z"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.Z")
|
|
};
|
|
|
|
if (DoubleChannels[0].Get())
|
|
{
|
|
DoubleChannels[0].Get()->SetDefault(Location.X);
|
|
ImportTransformChannelToDouble(Translation[0], DoubleChannels[0].Get(), FrameRate, false, true);
|
|
}
|
|
|
|
if (DoubleChannels[1].Get())
|
|
{
|
|
DoubleChannels[1].Get()->SetDefault(Location.Y);
|
|
ImportTransformChannelToDouble(Translation[1], DoubleChannels[1].Get(), FrameRate, false, true);
|
|
}
|
|
if (DoubleChannels[2].Get())
|
|
{
|
|
DoubleChannels[2].Get()->SetDefault(Location.Z);
|
|
ImportTransformChannelToDouble(Translation[2], DoubleChannels[2].Get(), FrameRate, false, true);
|
|
}
|
|
|
|
if (DoubleChannels[3].Get())
|
|
{
|
|
DoubleChannels[3].Get()->SetDefault(Rotation.X);
|
|
ImportTransformChannelToDouble(EulerRotation[0], DoubleChannels[3].Get(), FrameRate, false, true);
|
|
}
|
|
if (DoubleChannels[4].Get())
|
|
{
|
|
DoubleChannels[4].Get()->SetDefault(Rotation.Y);
|
|
ImportTransformChannelToDouble(EulerRotation[1], DoubleChannels[4].Get(), FrameRate, false, true);
|
|
}
|
|
if (DoubleChannels[5].Get())
|
|
{
|
|
DoubleChannels[5].Get()->SetDefault(Rotation.Z);
|
|
ImportTransformChannelToDouble(EulerRotation[2], DoubleChannels[5].Get(), FrameRate, false, true);
|
|
}
|
|
|
|
if (DoubleChannels[6].Get())
|
|
{
|
|
DoubleChannels[6].Get()->SetDefault(Scale3D.X);
|
|
ImportTransformChannelToDouble(Scale[0], DoubleChannels[6].Get(), FrameRate, false, true);
|
|
}
|
|
if (DoubleChannels[7].Get())
|
|
{
|
|
DoubleChannels[7].Get()->SetDefault(Scale3D.Y);
|
|
ImportTransformChannelToDouble(Scale[1], DoubleChannels[7].Get(), FrameRate, false, true);
|
|
}
|
|
if (DoubleChannels[8].Get())
|
|
{
|
|
DoubleChannels[8].Get()->SetDefault(Scale3D.Z);
|
|
ImportTransformChannelToDouble(Scale[2], DoubleChannels[8].Get(), FrameRate, false, true);
|
|
}
|
|
|
|
if(ImportFBXSettings->bCorrectForTransformOrigin)
|
|
{
|
|
CorrectForTransformOrigin(Player, TransformSection, Sequencer);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ImportFBXNode(FString NodeName, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, FGuid ObjectBinding, ISequencer* Sequencer)
|
|
{
|
|
// Look for animated float properties
|
|
TArray<FString> AnimatedPropertyNames;
|
|
CurveAPI.GetNodeAnimatedPropertyNameArray(NodeName, AnimatedPropertyNames);
|
|
|
|
for (auto AnimatedPropertyName : AnimatedPropertyNames)
|
|
{
|
|
ImportFBXProperty(NodeName, AnimatedPropertyName, ObjectBinding, CurveAPI, InSequence, Player, TemplateID);
|
|
}
|
|
|
|
ImportFBXTransform(NodeName, ObjectBinding, CurveAPI, InSequence, Player, Sequencer);
|
|
|
|
// Custom static string properties
|
|
TArray<TPair<FString, FString> > CustomPropertyPairs;
|
|
CurveAPI.GetCustomStringPropertyArray(NodeName, CustomPropertyPairs);
|
|
|
|
for (TPair<FString, FString>& CustomProperty : CustomPropertyPairs)
|
|
{
|
|
FMovieSceneToolsModule::Get().ImportStringProperty(CustomProperty.Key, CustomProperty.Value, ObjectBinding, InSequence->GetMovieScene());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MovieSceneToolHelpers::GetCameras( FbxNode* Parent, TArray<FbxCamera*>& Cameras )
|
|
{
|
|
FbxCamera* Camera = Parent->GetCamera();
|
|
if( Camera )
|
|
{
|
|
Cameras.Add(Camera);
|
|
}
|
|
|
|
int32 NodeCount = Parent->GetChildCount();
|
|
for ( int32 NodeIndex = 0; NodeIndex < NodeCount; ++NodeIndex )
|
|
{
|
|
FbxNode* Child = Parent->GetChild( NodeIndex );
|
|
GetCameras(Child, Cameras);
|
|
}
|
|
}
|
|
|
|
FbxCamera* FindCamera( FbxNode* Parent )
|
|
{
|
|
FbxCamera* Camera = Parent->GetCamera();
|
|
if( !Camera )
|
|
{
|
|
int32 NodeCount = Parent->GetChildCount();
|
|
for ( int32 NodeIndex = 0; NodeIndex < NodeCount && !Camera; ++NodeIndex )
|
|
{
|
|
FbxNode* Child = Parent->GetChild( NodeIndex );
|
|
Camera = Child->GetCamera();
|
|
}
|
|
}
|
|
|
|
return Camera;
|
|
}
|
|
|
|
FbxNode* RetrieveObjectFromName(const TCHAR* ObjectName, FbxNode* Root)
|
|
{
|
|
if (!Root)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
for (int32 ChildIndex=0;ChildIndex<Root->GetChildCount();++ChildIndex)
|
|
{
|
|
FbxNode* Node = Root->GetChild(ChildIndex);
|
|
if (Node)
|
|
{
|
|
FString NodeName = FString(Node->GetName());
|
|
|
|
if ( !FCString::Strcmp(ObjectName,UTF8_TO_TCHAR(Node->GetName())))
|
|
{
|
|
return Node;
|
|
}
|
|
|
|
if (FbxNode* NextNode = RetrieveObjectFromName(ObjectName,Node))
|
|
{
|
|
return NextNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void MovieSceneToolHelpers::CopyCameraProperties(FbxCamera* CameraNode, AActor* InCameraActor)
|
|
{
|
|
float FieldOfView;
|
|
float FocalLength;
|
|
|
|
if (CameraNode->GetApertureMode() == FbxCamera::eFocalLength)
|
|
{
|
|
FocalLength = CameraNode->FocalLength.Get();
|
|
FieldOfView = CameraNode->ComputeFieldOfView(FocalLength);
|
|
}
|
|
else
|
|
{
|
|
FieldOfView = CameraNode->FieldOfView.Get();
|
|
FocalLength = CameraNode->ComputeFocalLength(FieldOfView);
|
|
}
|
|
|
|
float ApertureWidth = CameraNode->GetApertureWidth();
|
|
float ApertureHeight = CameraNode->GetApertureHeight();
|
|
|
|
UCameraComponent* CameraComponent = nullptr;
|
|
|
|
if (ACineCameraActor* CineCameraActor = Cast<ACineCameraActor>(InCameraActor))
|
|
{
|
|
CameraComponent = CineCameraActor->GetCineCameraComponent();
|
|
|
|
UCineCameraComponent* CineCameraComponent = CineCameraActor->GetCineCameraComponent();
|
|
CineCameraComponent->Filmback.SensorWidth = FUnitConversion::Convert(ApertureWidth, EUnit::Inches, EUnit::Millimeters);
|
|
CineCameraComponent->Filmback.SensorHeight = FUnitConversion::Convert(ApertureHeight, EUnit::Inches, EUnit::Millimeters);
|
|
CineCameraComponent->FocusSettings.ManualFocusDistance = CameraNode->FocusDistance;
|
|
if (FocalLength < CineCameraComponent->LensSettings.MinFocalLength)
|
|
{
|
|
CineCameraComponent->LensSettings.MinFocalLength = FocalLength;
|
|
}
|
|
if (FocalLength > CineCameraComponent->LensSettings.MaxFocalLength)
|
|
{
|
|
CineCameraComponent->LensSettings.MaxFocalLength = FocalLength;
|
|
}
|
|
CineCameraComponent->CurrentFocalLength = FocalLength;
|
|
}
|
|
else if (ACameraActor* CameraActor = Cast<ACameraActor>(InCameraActor))
|
|
{
|
|
CameraComponent = CameraActor->GetCameraComponent();
|
|
}
|
|
|
|
if (!CameraComponent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CameraComponent->SetProjectionMode(CameraNode->ProjectionType.Get() == FbxCamera::ePerspective ? ECameraProjectionMode::Perspective : ECameraProjectionMode::Orthographic);
|
|
CameraComponent->SetAspectRatio(CameraNode->AspectWidth.Get() / CameraNode->AspectHeight.Get());
|
|
CameraComponent->SetOrthoNearClipPlane(CameraNode->NearPlane.Get());
|
|
CameraComponent->SetOrthoFarClipPlane(CameraNode->FarPlane.Get());
|
|
CameraComponent->SetOrthoWidth(CameraNode->OrthoZoom.Get());
|
|
CameraComponent->SetFieldOfView(FieldOfView);
|
|
}
|
|
|
|
FString MovieSceneToolHelpers::GetCameraName(FbxCamera* InCamera)
|
|
{
|
|
FbxNode* CameraNode = InCamera->GetNode();
|
|
if (CameraNode)
|
|
{
|
|
return CameraNode->GetName();
|
|
}
|
|
|
|
return InCamera->GetName();
|
|
}
|
|
|
|
|
|
void MovieSceneToolHelpers::ImportFBXCameraToExisting(UnFbx::FFbxImporter* FbxImporter, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap<FGuid, FString>& InObjectBindingMap, bool bMatchByNameOnly, bool bNotifySlate)
|
|
{
|
|
if (FApp::IsUnattended() || GIsRunningUnattendedScript)
|
|
{
|
|
bNotifySlate = false;
|
|
}
|
|
|
|
UMovieScene* MovieScene = InSequence->GetMovieScene();
|
|
|
|
for (auto InObjectBinding : InObjectBindingMap)
|
|
{
|
|
TArrayView<TWeakObjectPtr<>> BoundObjects = Player->FindBoundObjects(InObjectBinding.Key,TemplateID);
|
|
|
|
FString ObjectName = InObjectBinding.Value;
|
|
FbxCamera* CameraNode = nullptr;
|
|
FbxNode* Node = RetrieveObjectFromName(*ObjectName, FbxImporter->Scene->GetRootNode());
|
|
if (Node)
|
|
{
|
|
CameraNode = FindCamera(Node);
|
|
}
|
|
|
|
if (!CameraNode)
|
|
{
|
|
if (bMatchByNameOnly)
|
|
{
|
|
if (bNotifySlate)
|
|
{
|
|
FNotificationInfo Info(FText::Format(NSLOCTEXT("MovieSceneTools", "NoMatchingCameraError", "Failed to find any matching camera for {0}"), FText::FromString(ObjectName)));
|
|
Info.ExpireDuration = 5.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
CameraNode = FindCamera(FbxImporter->Scene->GetRootNode());
|
|
if (CameraNode)
|
|
{
|
|
if (bNotifySlate)
|
|
{
|
|
FString CameraName = GetCameraName(CameraNode);
|
|
FNotificationInfo Info(FText::Format(NSLOCTEXT("MovieSceneTools", "NoMatchingCameraWarning", "Failed to find any matching camera for {0}. Importing onto first camera from fbx {1}"), FText::FromString(ObjectName), FText::FromString(CameraName)));
|
|
Info.ExpireDuration = 5.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_None);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!CameraNode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
float FieldOfView;
|
|
float FocalLength;
|
|
|
|
if (CameraNode->GetApertureMode() == FbxCamera::eFocalLength)
|
|
{
|
|
FocalLength = CameraNode->FocalLength.Get();
|
|
FieldOfView = CameraNode->ComputeFieldOfView(FocalLength);
|
|
}
|
|
else
|
|
{
|
|
FieldOfView = CameraNode->FieldOfView.Get();
|
|
FocalLength = CameraNode->ComputeFocalLength(FieldOfView);
|
|
}
|
|
|
|
for (TWeakObjectPtr<>& WeakObject : BoundObjects)
|
|
{
|
|
UObject* FoundObject = WeakObject.Get();
|
|
if (FoundObject && FoundObject->GetClass()->IsChildOf(ACameraActor::StaticClass()))
|
|
{
|
|
CopyCameraProperties(CameraNode, Cast<AActor>(FoundObject));
|
|
|
|
UCameraComponent* CameraComponent = nullptr;
|
|
FName TrackName;
|
|
float TrackValue;
|
|
|
|
if (ACineCameraActor* CineCameraActor = Cast<ACineCameraActor>(FoundObject))
|
|
{
|
|
CameraComponent = CineCameraActor->GetCineCameraComponent();
|
|
TrackName = TEXT("CurrentFocalLength");
|
|
TrackValue = FocalLength;
|
|
}
|
|
else if (ACameraActor* CameraActor = Cast<ACameraActor>(FoundObject))
|
|
{
|
|
CameraComponent = CameraActor->GetCameraComponent();
|
|
TrackName = TEXT("FieldOfView");
|
|
TrackValue = FieldOfView;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Set the default value of the current focal length or field of view section
|
|
//FGuid PropertyOwnerGuid = Player->GetHandleToObject(CameraComponent);
|
|
FGuid PropertyOwnerGuid = GetHandleToObject(CameraComponent, InSequence, Player, TemplateID);
|
|
|
|
if (!PropertyOwnerGuid.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If copying properties to a spawnable object, the template object must be updated
|
|
MovieSceneHelpers::CopyObjectTemplate(InSequence, InObjectBinding.Key, FoundObject, Player->GetSharedPlaybackState());
|
|
|
|
UMovieSceneFloatTrack* FloatTrack = MovieScene->FindTrack<UMovieSceneFloatTrack>(PropertyOwnerGuid, TrackName);
|
|
if (FloatTrack)
|
|
{
|
|
FloatTrack->Modify();
|
|
FloatTrack->RemoveAllAnimationData();
|
|
|
|
bool bSectionAdded = false;
|
|
UMovieSceneFloatSection* FloatSection = Cast<UMovieSceneFloatSection>(FloatTrack->FindOrAddSection(0, bSectionAdded));
|
|
if (!FloatSection)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FloatSection->Modify();
|
|
|
|
if (bSectionAdded)
|
|
{
|
|
FloatSection->SetRange(TRange<FFrameNumber>::All());
|
|
}
|
|
|
|
FloatSection->GetChannelProxy().GetChannel<FMovieSceneFloatChannel>(0)->SetDefault(TrackValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImportFBXCamera(UnFbx::FFbxImporter* FbxImporter, UMovieSceneSequence* InSequence, ISequencer& InSequencer, TMap<FGuid, FString>& InObjectBindingMap, bool bMatchByNameOnly, bool bCreateCameras)
|
|
{
|
|
bool bNotifySlate = !FApp::IsUnattended() && !GIsRunningUnattendedScript;
|
|
|
|
UMovieScene* MovieScene = InSequence->GetMovieScene();
|
|
|
|
TArray<FbxCamera*> AllCameras;
|
|
MovieSceneToolHelpers::GetCameras(FbxImporter->Scene->GetRootNode(), AllCameras);
|
|
|
|
if (AllCameras.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bCreateCameras)
|
|
{
|
|
UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr;
|
|
|
|
// Find unmatched cameras
|
|
TArray<FbxCamera*> UnmatchedCameras;
|
|
for (auto Camera : AllCameras)
|
|
{
|
|
FString NodeName = MovieSceneToolHelpers::GetCameraName(Camera);
|
|
|
|
bool bMatched = false;
|
|
for (auto InObjectBinding : InObjectBindingMap)
|
|
{
|
|
FString ObjectName = InObjectBinding.Value;
|
|
if (ObjectName == NodeName)
|
|
{
|
|
// Look for a valid bound object, otherwise need to create a new camera and assign this binding to it
|
|
bool bFoundBoundObject = false;
|
|
TArrayView<TWeakObjectPtr<>> BoundObjects = InSequencer.FindBoundObjects(InObjectBinding.Key, InSequencer.GetFocusedTemplateID());
|
|
for (auto BoundObject : BoundObjects)
|
|
{
|
|
if (BoundObject.IsValid())
|
|
{
|
|
bFoundBoundObject = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFoundBoundObject)
|
|
{
|
|
if (bNotifySlate)
|
|
{
|
|
FNotificationInfo Info(FText::Format(NSLOCTEXT("MovieSceneTools", "NoBoundObjectsError", "Existing binding has no objects. Creating a new camera and binding for {0}"), FText::FromString(ObjectName)));
|
|
Info.ExpireDuration = 5.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bMatched)
|
|
{
|
|
UnmatchedCameras.Add(Camera);
|
|
}
|
|
}
|
|
|
|
// If there are new cameras, clear the object binding map so that we're only assigning values to the newly created cameras
|
|
if (UnmatchedCameras.Num() != 0)
|
|
{
|
|
InObjectBindingMap.Reset();
|
|
bMatchByNameOnly = true;
|
|
}
|
|
|
|
// Add any unmatched cameras
|
|
for (auto UnmatchedCamera : UnmatchedCameras)
|
|
{
|
|
FString CameraName = MovieSceneToolHelpers::GetCameraName(UnmatchedCamera);
|
|
|
|
AActor* NewCamera = nullptr;
|
|
if (UnmatchedCamera->GetApertureMode() == FbxCamera::eFocalLength)
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
NewCamera = World->SpawnActor<ACineCameraActor>(SpawnParams);
|
|
NewCamera->SetActorLabel(*CameraName);
|
|
}
|
|
else
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
NewCamera = World->SpawnActor<ACameraActor>(SpawnParams);
|
|
NewCamera->SetActorLabel(*CameraName);
|
|
}
|
|
|
|
// Copy camera properties before adding default tracks so that initial camera properties match and can be restored after sequencer finishes
|
|
MovieSceneToolHelpers::CopyCameraProperties(UnmatchedCamera, NewCamera);
|
|
|
|
TArray<TWeakObjectPtr<AActor> > NewCameras;
|
|
NewCameras.Add(NewCamera);
|
|
TArray<FGuid> NewCameraGuids = InSequencer.AddActors(NewCameras);
|
|
|
|
if (NewCameraGuids.Num())
|
|
{
|
|
InObjectBindingMap.Add(NewCameraGuids[0]);
|
|
InObjectBindingMap[NewCameraGuids[0]] = CameraName;
|
|
}
|
|
}
|
|
}
|
|
|
|
MovieSceneToolHelpers::ImportFBXCameraToExisting(FbxImporter, InSequence, &InSequencer, InSequencer.GetFocusedTemplateID(), InObjectBindingMap, bMatchByNameOnly, true);
|
|
}
|
|
|
|
FGuid FindCameraGuid(FbxCamera* Camera, TMap<FGuid, FString>& InObjectBindingMap)
|
|
{
|
|
FString CameraName = MovieSceneToolHelpers::GetCameraName(Camera);
|
|
|
|
for (auto& Pair : InObjectBindingMap)
|
|
{
|
|
if (Pair.Value == CameraName)
|
|
{
|
|
return Pair.Key;
|
|
}
|
|
}
|
|
return FGuid();
|
|
}
|
|
|
|
UMovieSceneCameraCutTrack* GetCameraCutTrack(UMovieScene* InMovieScene)
|
|
{
|
|
// Get the camera cut
|
|
UMovieSceneTrack* CameraCutTrack = InMovieScene->GetCameraCutTrack();
|
|
if (CameraCutTrack == nullptr)
|
|
{
|
|
InMovieScene->Modify();
|
|
CameraCutTrack = InMovieScene->AddCameraCutTrack(UMovieSceneCameraCutTrack::StaticClass());
|
|
}
|
|
return CastChecked<UMovieSceneCameraCutTrack>(CameraCutTrack);
|
|
}
|
|
|
|
void ImportCameraCut(UnFbx::FFbxImporter* FbxImporter, UMovieScene* InMovieScene, TMap<FGuid, FString>& InObjectBindingMap)
|
|
{
|
|
// Find a camera switcher
|
|
FbxCameraSwitcher* CameraSwitcher = FbxImporter->Scene->GlobalCameraSettings().GetCameraSwitcher();
|
|
if (CameraSwitcher == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
// Get the animation layer
|
|
FbxAnimStack* AnimStack = FbxImporter->Scene->GetMember<FbxAnimStack>(0);
|
|
if (AnimStack == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
FbxAnimLayer* AnimLayer = AnimStack->GetMember<FbxAnimLayer>(0);
|
|
if (AnimLayer == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// The camera switcher camera index refer to depth-first found order of the camera in the FBX
|
|
TArray<FbxCamera*> AllCameras;
|
|
MovieSceneToolHelpers::GetCameras(FbxImporter->Scene->GetRootNode(), AllCameras);
|
|
|
|
UMovieSceneCameraCutTrack* CameraCutTrack = GetCameraCutTrack(InMovieScene);
|
|
FFrameRate FrameRate = CameraCutTrack->GetTypedOuter<UMovieScene>()->GetTickResolution();
|
|
|
|
FbxAnimCurve* AnimCurve = CameraSwitcher->CameraIndex.GetCurve(AnimLayer);
|
|
if (AnimCurve)
|
|
{
|
|
for (int i = 0; i < AnimCurve->KeyGetCount(); ++i)
|
|
{
|
|
FbxAnimCurveKey key = AnimCurve->KeyGet(i);
|
|
int value = (int)key.GetValue() - 1;
|
|
if (value >= 0 && value < AllCameras.Num())
|
|
{
|
|
FGuid CameraGuid = FindCameraGuid(AllCameras[value], InObjectBindingMap);
|
|
if (CameraGuid != FGuid())
|
|
{
|
|
CameraCutTrack->AddNewCameraCut(UE::MovieScene::FRelativeObjectBindingID(CameraGuid), (key.GetTime().GetSecondDouble() * FrameRate).RoundToFrame());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class SMovieSceneImportFBXSettings : public SCompoundWidget, public FGCObject
|
|
{
|
|
SLATE_BEGIN_ARGS(SMovieSceneImportFBXSettings) {}
|
|
SLATE_ARGUMENT(FString, ImportFilename)
|
|
SLATE_ARGUMENT(UMovieSceneSequence*, Sequence)
|
|
SLATE_ARGUMENT(ISequencer*, Sequencer)
|
|
SLATE_END_ARGS()
|
|
|
|
void Construct(const FArguments& InArgs)
|
|
{
|
|
FPropertyEditorModule& PropertyEditor = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
DetailsViewArgs.bShowOptions = false;
|
|
DetailsViewArgs.bAllowSearch = false;
|
|
DetailsViewArgs.bShowPropertyMatrixButton = false;
|
|
DetailsViewArgs.bUpdatesFromSelection = false;
|
|
DetailsViewArgs.bLockable = false;
|
|
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
|
|
DetailsViewArgs.ViewIdentifier = "Import FBX Settings";
|
|
|
|
DetailView = PropertyEditor.CreateDetailView(DetailsViewArgs);
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
DetailView.ToSharedRef()
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Right)
|
|
.Padding(5.f)
|
|
[
|
|
SNew(SButton)
|
|
.ContentPadding(FMargin(10, 5))
|
|
.Text(NSLOCTEXT("MovieSceneTools", "ImportFBXButtonText", "Import"))
|
|
.OnClicked(this, &SMovieSceneImportFBXSettings::OnImportFBXClicked)
|
|
]
|
|
|
|
];
|
|
|
|
ImportFilename = InArgs._ImportFilename;
|
|
Sequence = InArgs._Sequence;
|
|
Sequencer = InArgs._Sequencer;
|
|
|
|
UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXSettings>();
|
|
DetailView->SetObject(ImportFBXSettings);
|
|
}
|
|
|
|
virtual void AddReferencedObjects( FReferenceCollector& Collector ) override
|
|
{
|
|
Collector.AddReferencedObject(Sequence);
|
|
}
|
|
virtual FString GetReferencerName() const override
|
|
{
|
|
return TEXT("SMovieSceneImportFBXSettings");
|
|
}
|
|
|
|
void SetObjectBindingMap(const TMap<FGuid, FString>& InObjectBindingMap)
|
|
{
|
|
ObjectBindingMap = InObjectBindingMap;
|
|
}
|
|
|
|
void SetCreateCameras(TOptional<bool> bInCreateCameras)
|
|
{
|
|
bCreateCameras = bInCreateCameras;
|
|
}
|
|
|
|
private:
|
|
|
|
FReply OnImportFBXClicked()
|
|
{
|
|
|
|
UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXSettings>();
|
|
FEditorDirectories::Get().SetLastDirectory( ELastDirectory::FBX, FPaths::GetPath( ImportFilename ) ); // Save path as default for next time.
|
|
|
|
if (!Sequence || !Sequence->GetMovieScene() || Sequence->GetMovieScene()->IsReadOnly())
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FFBXInOutParameters InOutParams;
|
|
if (!MovieSceneToolHelpers::ReadyFBXForImport(ImportFilename, ImportFBXSettings,InOutParams))
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("MovieSceneTools", "ImportFBXTransaction", "Import FBX"));
|
|
UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
|
|
bool bMatchByNameOnly = ImportFBXSettings->bMatchByNameOnly;
|
|
if (ObjectBindingMap.Num() == 1 && bMatchByNameOnly)
|
|
{
|
|
UE_LOG(LogMovieScene, Display, TEXT("Fbx Import: Importing onto one selected binding, disabling match by name only."));
|
|
bMatchByNameOnly = false;
|
|
}
|
|
|
|
// Import static cameras first
|
|
ImportFBXCamera(FbxImporter, Sequence, *Sequencer, ObjectBindingMap, bMatchByNameOnly, bCreateCameras.IsSet() ? bCreateCameras.GetValue() : ImportFBXSettings->bCreateCameras);
|
|
|
|
UWorld* World = Sequencer->GetPlaybackContext()->GetWorld();
|
|
bool bValid = MovieSceneToolHelpers::ImportFBXIfReady(World, Sequence, Sequencer, Sequencer->GetFocusedTemplateID(), ObjectBindingMap, ImportFBXSettings, InOutParams, Sequencer);
|
|
|
|
Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
|
|
|
|
TSharedPtr<SWindow> Window = FSlateApplication::Get().FindWidgetWindow(AsShared());
|
|
|
|
if ( Window.IsValid() )
|
|
{
|
|
Window->RequestDestroyWindow();
|
|
}
|
|
|
|
return bValid ? FReply::Handled() : FReply::Unhandled();
|
|
}
|
|
|
|
TSharedPtr<IDetailsView> DetailView;
|
|
FString ImportFilename;
|
|
TObjectPtr<UMovieSceneSequence> Sequence;
|
|
ISequencer* Sequencer;
|
|
TMap<FGuid, FString> ObjectBindingMap;
|
|
TOptional<bool> bCreateCameras;
|
|
|
|
};
|
|
|
|
bool MovieSceneToolHelpers::ReadyFBXForImport(const FString& ImportFilename, UMovieSceneUserImportFBXSettings* ImportFBXSettings, FFBXInOutParameters& OutParams)
|
|
{
|
|
UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
|
|
UnFbx::FBXImportOptions* ImportOptions = FbxImporter->GetImportOptions();
|
|
OutParams.bConvertSceneBackup = ImportOptions->bConvertScene;
|
|
OutParams.bConvertSceneUnitBackup = ImportOptions->bConvertSceneUnit;
|
|
OutParams.bForceFrontXAxisBackup = ImportOptions->bForceFrontXAxis;
|
|
OutParams.ImportUniformScaleBackup = ImportOptions->ImportUniformScale;
|
|
|
|
ImportOptions->bIsImportCancelable = false;
|
|
ImportOptions->bConvertScene = true;
|
|
ImportOptions->bConvertSceneUnit = ImportFBXSettings->bConvertSceneUnit;
|
|
ImportOptions->bForceFrontXAxis = ImportFBXSettings->bForceFrontXAxis;
|
|
ImportOptions->ImportUniformScale = ImportFBXSettings->ImportUniformScale;
|
|
|
|
const FString FileExtension = FPaths::GetExtension(ImportFilename);
|
|
if (!FbxImporter->ImportFromFile(*ImportFilename, FileExtension, true))
|
|
{
|
|
// Log the error message and fail the import.
|
|
FbxImporter->ReleaseScene();
|
|
ImportOptions->bConvertScene = OutParams.bConvertSceneBackup;
|
|
ImportOptions->bConvertSceneUnit = OutParams.bConvertSceneUnitBackup;
|
|
ImportOptions->bForceFrontXAxis = OutParams.bForceFrontXAxisBackup;
|
|
ImportOptions->ImportUniformScale = OutParams.ImportUniformScaleBackup;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ImportFBXOntoControlRigs(UWorld* World, UMovieScene* MovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID,
|
|
TMap<FGuid, FString>& ObjectBindingMap, const TArray<FString>& ControRigControlNames , UMovieSceneUserImportFBXSettings* ImportFBXSettings,
|
|
UMovieSceneUserImportFBXControlRigSettings* Settings)
|
|
{
|
|
UMovieSceneUserImportFBXSettings* CurrentImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXSettings>();
|
|
TArray<uint8> OriginalSettings;
|
|
FObjectWriter ObjWriter(CurrentImportFBXSettings, OriginalSettings);
|
|
|
|
CurrentImportFBXSettings->bMatchByNameOnly = ImportFBXSettings->bMatchByNameOnly;
|
|
CurrentImportFBXSettings->bForceFrontXAxis = ImportFBXSettings->bForceFrontXAxis;
|
|
CurrentImportFBXSettings->bCreateCameras = ImportFBXSettings->bCreateCameras;
|
|
CurrentImportFBXSettings->bReduceKeys = ImportFBXSettings->bReduceKeys;
|
|
CurrentImportFBXSettings->ReduceKeysTolerance = ImportFBXSettings->ReduceKeysTolerance;
|
|
CurrentImportFBXSettings->bConvertSceneUnit = ImportFBXSettings->bConvertSceneUnit;
|
|
CurrentImportFBXSettings->ImportUniformScale = ImportFBXSettings->ImportUniformScale;
|
|
|
|
|
|
UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ImportFBXIfReady(UWorld* World, UMovieSceneSequence* Sequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap<FGuid, FString>& ObjectBindingMap, UMovieSceneUserImportFBXSettings* ImportFBXSettings,
|
|
const FFBXInOutParameters& InParams, ISequencer* Sequencer)
|
|
{
|
|
UMovieScene* MovieScene = Sequence->GetMovieScene();
|
|
|
|
UMovieSceneUserImportFBXSettings* CurrentImportFBXSettings = GetMutableDefault<UMovieSceneUserImportFBXSettings>();
|
|
TArray<uint8> OriginalSettings;
|
|
FObjectWriter ObjWriter(CurrentImportFBXSettings, OriginalSettings);
|
|
|
|
CurrentImportFBXSettings->bMatchByNameOnly = ImportFBXSettings->bMatchByNameOnly;
|
|
CurrentImportFBXSettings->bForceFrontXAxis = ImportFBXSettings->bForceFrontXAxis;
|
|
CurrentImportFBXSettings->bCreateCameras = ImportFBXSettings->bCreateCameras;
|
|
CurrentImportFBXSettings->bReduceKeys = ImportFBXSettings->bReduceKeys;
|
|
CurrentImportFBXSettings->ReduceKeysTolerance = ImportFBXSettings->ReduceKeysTolerance;
|
|
CurrentImportFBXSettings->bConvertSceneUnit = ImportFBXSettings->bConvertSceneUnit;
|
|
CurrentImportFBXSettings->ImportUniformScale = ImportFBXSettings->ImportUniformScale;
|
|
CurrentImportFBXSettings->bReplaceTransformTrack = ImportFBXSettings->bReplaceTransformTrack;
|
|
CurrentImportFBXSettings->bCorrectForTransformOrigin = ImportFBXSettings->bCorrectForTransformOrigin;
|
|
UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
|
|
UnFbx::FFbxCurvesAPI CurveAPI;
|
|
FbxImporter->PopulateAnimatedCurveData(CurveAPI);
|
|
TArray<FString> AllNodeNames;
|
|
CurveAPI.GetAllNodeNameArray(AllNodeNames);
|
|
|
|
// Import a camera cut track if cams were created, do it after populating curve data ensure only one animation layer, if any
|
|
ImportCameraCut(FbxImporter, MovieScene, ObjectBindingMap);
|
|
|
|
FString RootNodeName = FbxImporter->Scene->GetRootNode()->GetName();
|
|
|
|
// First try matching by name
|
|
for (int32 NodeIndex = 0; NodeIndex < AllNodeNames.Num(); )
|
|
{
|
|
FString NodeName = AllNodeNames[NodeIndex];
|
|
if (RootNodeName == NodeName)
|
|
{
|
|
++NodeIndex;
|
|
continue;
|
|
}
|
|
|
|
bool bFoundMatch = false;
|
|
for (auto It = ObjectBindingMap.CreateConstIterator(); It; ++It)
|
|
{
|
|
if (FCString::Strcmp(*It.Value().ToUpper(), *NodeName.ToUpper()) == 0)
|
|
{
|
|
MovieSceneToolHelpers::ImportFBXNode(NodeName, CurveAPI, Sequence, Player, TemplateID, It.Key(), Sequencer);
|
|
|
|
ObjectBindingMap.Remove(It.Key());
|
|
AllNodeNames.RemoveAt(NodeIndex);
|
|
|
|
bFoundMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bFoundMatch)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
++NodeIndex;
|
|
}
|
|
|
|
// Otherwise, get the first available node that hasn't been imported onto yet
|
|
if (!ImportFBXSettings->bMatchByNameOnly)
|
|
{
|
|
for (int32 NodeIndex = 0; NodeIndex < AllNodeNames.Num(); )
|
|
{
|
|
FString NodeName = AllNodeNames[NodeIndex];
|
|
if (RootNodeName == NodeName)
|
|
{
|
|
++NodeIndex;
|
|
continue;
|
|
}
|
|
|
|
auto It = ObjectBindingMap.CreateConstIterator();
|
|
if (It)
|
|
{
|
|
MovieSceneToolHelpers::ImportFBXNode(NodeName, CurveAPI, Sequence, Player, TemplateID, It.Key(), Sequencer);
|
|
|
|
UE_LOG(LogMovieScene, Warning, TEXT("Fbx Import: Failed to find any matching node for (%s). Defaulting to first available (%s)."), *NodeName, *It.Value());
|
|
ObjectBindingMap.Remove(It.Key());
|
|
AllNodeNames.RemoveAt(NodeIndex);
|
|
continue;
|
|
}
|
|
|
|
++NodeIndex;
|
|
}
|
|
}
|
|
|
|
for (FString NodeName : AllNodeNames)
|
|
{
|
|
UE_LOG(LogMovieScene, Warning, TEXT("Fbx Import: Failed to find any matching node for (%s)."), *NodeName);
|
|
}
|
|
|
|
// restore
|
|
FObjectReader ObjReader(GetMutableDefault<UMovieSceneUserImportFBXSettings>(), OriginalSettings);
|
|
|
|
FbxImporter->ReleaseScene();
|
|
UnFbx::FBXImportOptions* ImportOptions = FbxImporter->GetImportOptions();
|
|
ImportOptions->bConvertScene = InParams.bConvertSceneBackup;
|
|
ImportOptions->bConvertSceneUnit = InParams.bConvertSceneUnitBackup;
|
|
ImportOptions->bForceFrontXAxis = InParams.bForceFrontXAxisBackup;
|
|
ImportOptions->ImportUniformScale = InParams.ImportUniformScaleBackup;
|
|
return true;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ImportFBXWithDialog(UMovieSceneSequence* InSequence, ISequencer& InSequencer, const TMap<FGuid, FString>& InObjectBindingMap, TOptional<bool> bCreateCameras)
|
|
{
|
|
TArray<FString> OpenFilenames;
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bOpen = false;
|
|
if (DesktopPlatform)
|
|
{
|
|
FString ExtensionStr;
|
|
ExtensionStr += TEXT("FBX (*.fbx)|*.fbx|");
|
|
|
|
bOpen = DesktopPlatform->OpenFileDialog(
|
|
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
|
|
NSLOCTEXT("MovieSceneTools", "ImportFBX", "Import FBX from...").ToString(),
|
|
FEditorDirectories::Get().GetLastDirectory(ELastDirectory::FBX),
|
|
TEXT(""),
|
|
*ExtensionStr,
|
|
EFileDialogFlags::None,
|
|
OpenFilenames
|
|
);
|
|
}
|
|
if (!bOpen)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!OpenFilenames.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FText TitleText = NSLOCTEXT("MovieSceneTools", "ImportFBXTitle", "Import FBX");
|
|
|
|
// Create the window to choose our options
|
|
TSharedRef<SWindow> Window = SNew(SWindow)
|
|
.Title(TitleText)
|
|
.HasCloseButton(true)
|
|
.SizingRule(ESizingRule::UserSized)
|
|
.ClientSize(FVector2D(450.0f, 300.0f))
|
|
.AutoCenter(EAutoCenter::PreferredWorkArea)
|
|
.SupportsMinimize(false);
|
|
|
|
TSharedRef<SMovieSceneImportFBXSettings> DialogWidget = SNew(SMovieSceneImportFBXSettings)
|
|
.ImportFilename(OpenFilenames[0])
|
|
.Sequence(InSequence)
|
|
.Sequencer(&InSequencer);
|
|
DialogWidget->SetObjectBindingMap(InObjectBindingMap);
|
|
DialogWidget->SetCreateCameras(bCreateCameras);
|
|
Window->SetContent(DialogWidget);
|
|
|
|
FSlateApplication::Get().AddWindow(Window);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::HasHiddenMobility(const UClass* ObjectClass)
|
|
{
|
|
if (ObjectClass)
|
|
{
|
|
static const FName NAME_HideCategories(TEXT("HideCategories"));
|
|
if (ObjectClass->HasMetaData(NAME_HideCategories))
|
|
{
|
|
if (ObjectClass->GetMetaData(NAME_HideCategories).Contains(TEXT("Mobility")))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const FMovieSceneEvaluationTrack* MovieSceneToolHelpers::GetEvaluationTrack(ISequencer *Sequencer, const FGuid& TrackSignature)
|
|
{
|
|
FMovieSceneRootEvaluationTemplateInstance& Instance = Sequencer->GetEvaluationTemplate();
|
|
FMovieSceneCompiledDataID SubDataID = Instance.GetCompiledDataManager()->GetSubDataID(Instance.GetCompiledDataID(), Sequencer->GetFocusedTemplateID());
|
|
|
|
{
|
|
const FMovieSceneEvaluationTemplate* Template = SubDataID.IsValid() ? Instance.GetCompiledDataManager()->FindTrackTemplate(SubDataID) : nullptr;
|
|
const FMovieSceneEvaluationTrack* EvalTrack = Template ? Template->FindTrack(TrackSignature) : nullptr;
|
|
if (EvalTrack)
|
|
{
|
|
return EvalTrack;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ExportLevelMesh(UnFbx::FFbxExporter* Exporter, ULevel* Level, IMovieScenePlayer* Player, const TArray<FGuid>& Bindings, INodeNameAdapter& NodeNameAdapter, FMovieSceneSequenceIDRef& Template)
|
|
{
|
|
// Get list of actors based upon bindings...
|
|
const bool bSelectedOnly = (Bindings.Num()) != 0;
|
|
|
|
const bool bSaveAnimSeq = false; //force off saving any AnimSequences since this can conflict when we export the level sequence animations.
|
|
|
|
TArray<AActor*> ActorToExport;
|
|
|
|
int32 ActorCount = Level->Actors.Num();
|
|
for (int32 ActorIndex = 0; ActorIndex < ActorCount; ++ActorIndex)
|
|
{
|
|
AActor* Actor = Level->Actors[ActorIndex];
|
|
if (Actor != NULL)
|
|
{
|
|
FGuid ExistingGuid = Player->FindObjectId(*Actor, Template);
|
|
if (ExistingGuid.IsValid() && (!bSelectedOnly || Bindings.Contains(ExistingGuid)))
|
|
{
|
|
ActorToExport.Add(Actor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export the persistent level and all of it's actors
|
|
Exporter->ExportLevelMesh(Level, !bSelectedOnly, ActorToExport, NodeNameAdapter, bSaveAnimSeq);
|
|
}
|
|
|
|
//5.5 deprecrated
|
|
bool MovieSceneToolHelpers::ExportFBX(UWorld* World, UMovieScene* MovieScene, IMovieScenePlayer* Player, const TArray<FGuid>& Bindings, const TArray<UMovieSceneTrack*>& Tracks, INodeNameAdapter& NodeNameAdapter, FMovieSceneSequenceIDRef& Template, const FString& InFBXFileName, FMovieSceneSequenceTransform& RootToLocalTransform)
|
|
{
|
|
if (MovieScene)
|
|
{
|
|
if (UMovieSceneSequence* MovieSceneSequence = MovieScene->GetTypedOuter< UMovieSceneSequence>())
|
|
{
|
|
FAnimExportSequenceParameters AESP;
|
|
AESP.MovieSceneSequence = MovieSceneSequence;
|
|
AESP.RootMovieSceneSequence = MovieSceneSequence;
|
|
AESP.Player = Player;
|
|
AESP.RootToLocalTransform = RootToLocalTransform;
|
|
return MovieSceneToolHelpers::ExportFBX(World, AESP, Bindings, Tracks, NodeNameAdapter, Template, InFBXFileName);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ExportFBX(UWorld* World, const FAnimExportSequenceParameters& AESP, const TArray<FGuid>& Bindings, const TArray<UMovieSceneTrack*>& Tracks, INodeNameAdapter& NodeNameAdapter, FMovieSceneSequenceIDRef& Template, const FString& InFBXFileName)
|
|
{
|
|
UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance();
|
|
|
|
Exporter->CreateDocument();
|
|
Exporter->SetTransformBaking(false);
|
|
Exporter->SetKeepHierarchy(true);
|
|
|
|
ExportLevelMesh(Exporter, World->PersistentLevel, AESP.Player, Bindings, NodeNameAdapter, Template);
|
|
|
|
// Export streaming levels and actors
|
|
for (ULevelStreaming* StreamingLevel : World->GetStreamingLevels())
|
|
{
|
|
if (StreamingLevel)
|
|
{
|
|
if (ULevel* Level = StreamingLevel->GetLoadedLevel())
|
|
{
|
|
ExportLevelMesh(Exporter, Level, AESP.Player, Bindings, NodeNameAdapter, Template);
|
|
}
|
|
}
|
|
}
|
|
|
|
Exporter->ExportLevelSequence(AESP.MovieSceneSequence, AESP.RootMovieSceneSequence, Bindings, AESP.Player, NodeNameAdapter, Template, AESP.RootToLocalTransform);
|
|
|
|
//Export given tracks
|
|
Exporter->ExportLevelSequenceTracks(AESP.MovieSceneSequence, AESP.RootMovieSceneSequence, AESP.Player, Template, nullptr, nullptr, Tracks, AESP.RootToLocalTransform);
|
|
|
|
// Save to disk
|
|
Exporter->WriteToFile(*InFBXFileName);
|
|
|
|
return true;
|
|
}
|
|
static void TickLiveLink(ILiveLinkClient* LiveLinkClient, TMap<FGuid, ELiveLinkSourceMode>& SourceAndMode)
|
|
{
|
|
|
|
//This first bit lookes for a Sequencer Live Link Source which can show up any frame and we need to set it to Latest mode
|
|
if (LiveLinkClient)
|
|
{
|
|
TArray<FGuid> Sources = LiveLinkClient->GetSources();
|
|
for (const FGuid& Guid : Sources)
|
|
{
|
|
FText SourceTypeText = LiveLinkClient->GetSourceType(Guid);
|
|
FString SourceTypeStr = SourceTypeText.ToString();
|
|
if (SourceTypeStr.Contains(TEXT("Sequencer Live Link")))
|
|
{
|
|
ULiveLinkSourceSettings* Settings = LiveLinkClient->GetSourceSettings(Guid);
|
|
if (Settings)
|
|
{
|
|
if (Settings->Mode != ELiveLinkSourceMode::Latest)
|
|
{
|
|
SourceAndMode.Add(Guid, Settings->Mode);
|
|
Settings->Mode = ELiveLinkSourceMode::Latest;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LiveLinkClient->ForceTick();
|
|
}
|
|
}
|
|
|
|
static void TickFrame(const FFrameNumber& FrameNumber,float DeltaTime, UMovieScene* MovieScene, UnFbx::FLevelSequenceAnimTrackAdapter& AnimTrackAdapter,
|
|
const TArray<IMovieSceneToolsAnimationBakeHelper*>& BakeHelpers, const TArray< USkeletalMeshComponent*>& SkelMeshComps,
|
|
ILiveLinkClient* LiveLinkClient, TMap<FGuid, ELiveLinkSourceMode>& SourceAndMode)
|
|
{
|
|
//Begin records a frame so need to set things up first
|
|
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
|
|
{
|
|
if (BakeHelper)
|
|
{
|
|
BakeHelper->PreEvaluation(MovieScene, FrameNumber);
|
|
}
|
|
}
|
|
// This will call UpdateSkelPose on the skeletal mesh component to move bones based on animations in the sequence
|
|
int32 Index = FrameNumber.Value;
|
|
AnimTrackAdapter.UpdateAnimation(Index);
|
|
|
|
UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr;
|
|
if (World)
|
|
{
|
|
const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(World);
|
|
Controller.EvaluateAllConstraints();
|
|
}
|
|
|
|
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
|
|
{
|
|
if (BakeHelper)
|
|
{
|
|
BakeHelper->PostEvaluation(MovieScene, FrameNumber);
|
|
}
|
|
}
|
|
//Live Link source can show up at any time so we unfortunately need to check for it
|
|
TickLiveLink(LiveLinkClient, SourceAndMode);
|
|
|
|
// Update space bases so new animation position has an effect.
|
|
for (USkeletalMeshComponent* SkelMeshComp : SkelMeshComps)
|
|
{
|
|
SkelMeshComp->SetForcedLOD(1); //we need to do this every tick since a restore state may happen while evaluating and bummp us out ot a different LOD
|
|
SkelMeshComp->TickAnimation(DeltaTime, false);
|
|
|
|
SkelMeshComp->RefreshBoneTransforms();
|
|
SkelMeshComp->RefreshFollowerComponents();
|
|
SkelMeshComp->UpdateComponentToWorld();
|
|
SkelMeshComp->FinalizeBoneTransform();
|
|
SkelMeshComp->MarkRenderTransformDirty();
|
|
SkelMeshComp->MarkRenderDynamicDataDirty();
|
|
}
|
|
|
|
}
|
|
bool MovieSceneToolHelpers::BakeToSkelMeshToCallbacks(UMovieScene* MovieScene, IMovieScenePlayer* Player,
|
|
USkeletalMeshComponent* InSkelMeshComp, FMovieSceneSequenceIDRef& Template, FMovieSceneSequenceTransform& RootToLocalTransform, UAnimSeqExportOption* ExportOptions,
|
|
FInitAnimationCB InitCallback, FStartAnimationCB StartCallback, FTickAnimationCB TickCallback, FEndAnimationCB EndCallback)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
bool MovieSceneToolHelpers::BakeToSkelMeshToCallbacks(const FAnimExportSequenceParameters& AESP, USkeletalMeshComponent* InSkelMeshComp, UAnimSeqExportOption* ExportOptions,
|
|
FInitAnimationCB InitCallback, FStartAnimationCB StartCallback, FTickAnimationCB TickCallback, FEndAnimationCB EndCallback)
|
|
{
|
|
UMovieScene* MovieScene = AESP.MovieSceneSequence->GetMovieScene();
|
|
TArray< USkeletalMeshComponent*> SkelMeshComps;
|
|
if (!ExportOptions)
|
|
{
|
|
UE_LOG(LogMovieScene, Warning, TEXT(" MovieSceneToolHelpers::BakeToSkelMesh functions require a valid AnimSeqExportOption"));
|
|
return false;
|
|
}
|
|
if (ExportOptions->bEvaluateAllSkeletalMeshComponents)
|
|
{
|
|
AActor* Actor = InSkelMeshComp->GetTypedOuter<AActor>();
|
|
if (Actor)
|
|
{
|
|
Actor->GetComponents(SkelMeshComps, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SkelMeshComps.Add(InSkelMeshComp);
|
|
}
|
|
//if we have no allocated bone space transforms something wrong so try to recalc them,only need to do this on the recorded skelmesh
|
|
if (InSkelMeshComp->GetBoneSpaceTransforms().Num() <= 0)
|
|
{
|
|
InSkelMeshComp->RecalcRequiredBones(0);
|
|
if (InSkelMeshComp->GetBoneSpaceTransforms().Num() <= 0)
|
|
{
|
|
UE_LOG(LogMovieScene, Error, TEXT("Error Ba"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
UnFbx::FLevelSequenceAnimTrackAdapter::FAnimTrackSettings AnimTrackSettings;
|
|
AnimTrackSettings.MovieScenePlayer = AESP.Player;
|
|
AnimTrackSettings.MovieSceneSequence = AESP.MovieSceneSequence;
|
|
AnimTrackSettings.RootMovieSceneSequence = AESP.RootMovieSceneSequence;
|
|
AnimTrackSettings.RootToLocalTransform = AESP.RootToLocalTransform;
|
|
AnimTrackSettings.bForceUseOfMovieScenePlaybackRange = AESP.bForceUseOfMovieScenePlaybackRange;
|
|
|
|
UnFbx::FLevelSequenceAnimTrackAdapter AnimTrackAdapter(AnimTrackSettings);
|
|
if (ExportOptions->bUseCustomTimeRange)
|
|
{
|
|
const FFrameRate TickResolution = MovieScene->GetTickResolution();
|
|
const FFrameRate DisplayResolution = ExportOptions->CustomDisplayRate;
|
|
const FFrameNumber StartFrameInTick = FFrameRate::TransformTime(FFrameTime(ExportOptions->CustomStartFrame), DisplayResolution, TickResolution).FloorToFrame();
|
|
FFrameNumber EndFrameInTick = FFrameRate::TransformTime(FFrameTime(ExportOptions->CustomEndFrame), DisplayResolution, TickResolution).CeilToFrame();
|
|
if (EndFrameInTick < StartFrameInTick)
|
|
{
|
|
EndFrameInTick = StartFrameInTick;
|
|
}
|
|
AnimTrackAdapter.SetRange(StartFrameInTick,EndFrameInTick);
|
|
}
|
|
if (ExportOptions->bUseCustomFrameRate)
|
|
{
|
|
AnimTrackAdapter.SetFrameRate(ExportOptions->CustomFrameRate);
|
|
}
|
|
|
|
int32 LocalStartFrame = AnimTrackAdapter.GetLocalStartFrame();
|
|
int32 AnimationLength = AnimTrackAdapter.GetLength();
|
|
float FrameRate = AnimTrackAdapter.GetFrameRate();
|
|
float DeltaTime = 1.0f / FrameRate;
|
|
|
|
//If we are running with a live link track we need to do a few things.
|
|
// 1. First test to see if we have one, only way to really do that is to see if we have a source that has the `Sequencer Live Link Track`. We also evalute the first frame in case we are out of range and the sources aren't created yet.
|
|
// 2. Make sure Sequencer.AlwaysSendInterpolated.LiveLink is non-zero, and then set it back to zero if it's not.
|
|
// 3. For each live link sequencer source we need to set the ELiveLinkSourceMode to Latest so that we just get the latest and don't use engine/timecode for any interpolation.
|
|
ILiveLinkClient* LiveLinkClient = nullptr;
|
|
IModularFeatures& ModularFeatures = IModularFeatures::Get();
|
|
TMap<FGuid, ELiveLinkSourceMode> SourceAndMode;
|
|
if (ModularFeatures.IsModularFeatureAvailable(ILiveLinkClient::ModularFeatureName))
|
|
{
|
|
LiveLinkClient = &ModularFeatures.GetModularFeature<ILiveLinkClient>(ILiveLinkClient::ModularFeatureName);
|
|
}
|
|
TOptional<int32> SequencerAlwaysSenedLiveLinkInterpolated;
|
|
IConsoleVariable* CVarAlwaysSendInterpolatedLiveLink = IConsoleManager::Get().FindConsoleVariable(TEXT("Sequencer.AlwaysSendInterpolatedLiveLink"));
|
|
if (CVarAlwaysSendInterpolatedLiveLink)
|
|
{
|
|
SequencerAlwaysSenedLiveLinkInterpolated = CVarAlwaysSendInterpolatedLiveLink->GetInt();
|
|
CVarAlwaysSendInterpolatedLiveLink->Set(1, ECVF_SetByConsole);
|
|
}
|
|
|
|
const TArray<IMovieSceneToolsAnimationBakeHelper*>& BakeHelpers = FMovieSceneToolsModule::Get().GetAnimationBakeHelpers();
|
|
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
|
|
{
|
|
if (BakeHelper)
|
|
{
|
|
BakeHelper->StartBaking(MovieScene);
|
|
}
|
|
}
|
|
|
|
InitCallback.ExecuteIfBound();
|
|
//if we have delay frames run them first at the LocalStartFrame - ExportOptions->WarmupFrames;
|
|
if (ExportOptions->DelayBeforeStart > 0)
|
|
{
|
|
FFrameNumber FrameNumber = FFrameNumber(LocalStartFrame) - ExportOptions->WarmUpFrames;
|
|
for (int32 Index = 0; Index < ExportOptions->DelayBeforeStart; ++Index)
|
|
{
|
|
TickFrame(FrameNumber, DeltaTime, MovieScene, AnimTrackAdapter, BakeHelpers, SkelMeshComps, LiveLinkClient, SourceAndMode);
|
|
}
|
|
|
|
}
|
|
//if we have warmup frames
|
|
if (ExportOptions->WarmUpFrames > 0)
|
|
{
|
|
for (int32 Index = -ExportOptions->WarmUpFrames.Value; Index < 0; ++Index)
|
|
{
|
|
FFrameNumber FrameNumber = FFrameNumber(LocalStartFrame) - FFrameNumber(Index);
|
|
TickFrame(FrameNumber, DeltaTime, MovieScene, AnimTrackAdapter, BakeHelpers, SkelMeshComps, LiveLinkClient, SourceAndMode);
|
|
}
|
|
}
|
|
|
|
//BeginRecording, which happens in the Start Callback below, records a frame so need to set things up first at first frame, even if we had any delay or warmup
|
|
TickFrame(LocalStartFrame, DeltaTime, MovieScene, AnimTrackAdapter, BakeHelpers, SkelMeshComps, LiveLinkClient, SourceAndMode);
|
|
StartCallback.ExecuteIfBound(LocalStartFrame);
|
|
|
|
for (int32 FrameCount = 1; FrameCount <= AnimationLength; ++FrameCount)
|
|
{
|
|
int32 LocalIndex = LocalStartFrame + FrameCount;
|
|
FFrameNumber LocalFrame(LocalIndex);
|
|
TickFrame(LocalFrame, DeltaTime, MovieScene, AnimTrackAdapter, BakeHelpers, SkelMeshComps, LiveLinkClient, SourceAndMode);
|
|
TickCallback.ExecuteIfBound(DeltaTime, LocalFrame);
|
|
}
|
|
|
|
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
|
|
{
|
|
if (BakeHelper)
|
|
{
|
|
BakeHelper->StopBaking(MovieScene);
|
|
}
|
|
}
|
|
EndCallback.ExecuteIfBound();
|
|
|
|
//now do any sequencer live link cleanup
|
|
if (LiveLinkClient)
|
|
{
|
|
for (TPair<FGuid, ELiveLinkSourceMode>& Item : SourceAndMode)
|
|
{
|
|
ULiveLinkSourceSettings* Settings = LiveLinkClient->GetSourceSettings(Item.Key);
|
|
if (Settings)
|
|
{
|
|
Settings->Mode = Item.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SequencerAlwaysSenedLiveLinkInterpolated.IsSet() && CVarAlwaysSendInterpolatedLiveLink)
|
|
{
|
|
CVarAlwaysSendInterpolatedLiveLink->Set(0, ECVF_SetByConsole);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//5.5 deprecated
|
|
bool MovieSceneToolHelpers::ExportToAnimSequence(UAnimSequence* AnimSequence, UAnimSeqExportOption* ExportOptions, UMovieScene* MovieScene, IMovieScenePlayer* Player,
|
|
USkeletalMeshComponent* SkelMesh, FMovieSceneSequenceIDRef& Template, FMovieSceneSequenceTransform& RootToLocalTransform)
|
|
{
|
|
if (MovieScene)
|
|
{
|
|
FAnimExportSequenceParameters AESP;
|
|
AESP.Player = Player;
|
|
AESP.RootToLocalTransform = RootToLocalTransform;
|
|
UMovieSceneSequence* OwnerSceneSequence = MovieScene->GetTypedOuter<UMovieSceneSequence>();
|
|
AESP.MovieSceneSequence = OwnerSceneSequence;
|
|
AESP.RootMovieSceneSequence = OwnerSceneSequence;
|
|
return MovieSceneToolHelpers::ExportToAnimSequence(AnimSequence, ExportOptions, AESP, SkelMesh);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves a string from the MovieSceneSequence corresponding to the source takename.
|
|
*
|
|
* It tries to find the UTakeMetaData slate via reflection, since UTakeMetaData is defined in a plugin.
|
|
* If that fails, it returns the name of the MovieSceneSequence.
|
|
*/
|
|
static FString GetSlateFromMovieSceneSequence(UMovieSceneSequence* MovieSceneSequence)
|
|
{
|
|
if (!MovieSceneSequence)
|
|
{
|
|
return FString();
|
|
}
|
|
|
|
ULevelSequence* LevelSequence = Cast<ULevelSequence>(MovieSceneSequence);
|
|
|
|
if (!LevelSequence)
|
|
{
|
|
return MovieSceneSequence->GetName();
|
|
}
|
|
|
|
// Find the UTakeMetaData class dynamically, since it is defined in a plugin.
|
|
static UClass* TakeMetaDataClass = FindObject<UClass>(nullptr, TEXT("/Script/TakesCore.TakeMetaData"), false);
|
|
if (!TakeMetaDataClass)
|
|
{
|
|
return LevelSequence->GetName();
|
|
}
|
|
|
|
if (UObject* MetaDataObject = LevelSequence->FindMetaDataByClass(TakeMetaDataClass))
|
|
{
|
|
if (const FProperty* SlateProperty = TakeMetaDataClass->FindPropertyByName("Slate"))
|
|
{
|
|
if (const FStrProperty* SlateStrProperty = CastField<FStrProperty>(SlateProperty))
|
|
{
|
|
return SlateStrProperty->GetPropertyValue_InContainer(MetaDataObject);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMovieScene, Error, TEXT("TakesMetaData::Slate property was found, but it unexpectedly was not a String property."));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMovieScene, Error, TEXT("TakesMetaData class was found, but it unexpectedly did not have a Slate property."));
|
|
}
|
|
}
|
|
|
|
return LevelSequence->GetName();
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::ExportToAnimSequence(UAnimSequence* AnimSequence, UAnimSeqExportOption* ExportOptions, const FAnimExportSequenceParameters& AESP,
|
|
USkeletalMeshComponent* SkelMeshComp)
|
|
{
|
|
if (AnimSequence == nullptr || ExportOptions == nullptr || AESP.MovieSceneSequence == nullptr || AESP.RootMovieSceneSequence == nullptr || SkelMeshComp == nullptr)
|
|
{
|
|
UE_LOG(LogMovieScene, Error, TEXT("MovieSceneToolHelpers::ExportToAnimSequence All parameters must be valid."));
|
|
return false;
|
|
}
|
|
|
|
FAnimRecorderInstance AnimationRecorder;
|
|
const FFrameRate SampleRate = ExportOptions->bUseCustomFrameRate ? ExportOptions->CustomFrameRate : AESP.MovieSceneSequence->GetMovieScene()->GetDisplayRate();
|
|
// Used by function that retrieves the current timecode
|
|
FQualifiedFrameTime CurrentTimecode;
|
|
|
|
// Prefer the rate overriden by the user, otherwise use the project setting.
|
|
if (ExportOptions->bTimecodeRateOverride)
|
|
{
|
|
CurrentTimecode.Rate = ExportOptions->OverrideTimecodeRate;
|
|
}
|
|
else if (GEngine)
|
|
{
|
|
CurrentTimecode.Rate = GEngine->GenerateDefaultTimecodeFrameRate;
|
|
}
|
|
|
|
FInitAnimationCB InitCallback = FInitAnimationCB::CreateLambda([&AnimationRecorder, SampleRate, ExportOptions, SkelMeshComp, AnimSequence]
|
|
{
|
|
FAnimationRecordingSettings RecordingSettings;
|
|
RecordingSettings.SampleFrameRate = SampleRate;
|
|
RecordingSettings.Interpolation = ExportOptions->Interpolation;
|
|
RecordingSettings.InterpMode = ExportOptions->CurveInterpolation;
|
|
if (ExportOptions->CurveInterpolation == ERichCurveInterpMode::RCIM_Constant ||
|
|
ExportOptions->CurveInterpolation == ERichCurveInterpMode::RCIM_None)
|
|
{
|
|
RecordingSettings.TangentMode = ERichCurveTangentMode::RCTM_None;
|
|
}
|
|
else
|
|
{
|
|
RecordingSettings.TangentMode = ERichCurveTangentMode::RCTM_Auto;
|
|
}
|
|
RecordingSettings.Length = 0;
|
|
RecordingSettings.bRemoveRootAnimation = false;
|
|
RecordingSettings.bCheckDeltaTimeAtBeginning = false;
|
|
RecordingSettings.bRecordTransforms = ExportOptions->bExportTransforms;
|
|
RecordingSettings.bRecordMorphTargets = ExportOptions->bExportMorphTargets;
|
|
RecordingSettings.bRecordAttributeCurves = ExportOptions->bExportAttributeCurves;
|
|
RecordingSettings.bRecordMaterialCurves = ExportOptions->bExportMaterialCurves;
|
|
RecordingSettings.bRecordInWorldSpace = ExportOptions->bRecordInWorldSpace;
|
|
RecordingSettings.IncludeAnimationNames = ExportOptions->IncludeAnimationNames;
|
|
RecordingSettings.ExcludeAnimationNames = ExportOptions->ExcludeAnimationNames;
|
|
RecordingSettings.bTransactRecording = ExportOptions->bTransactRecording;
|
|
AnimationRecorder.Init(SkelMeshComp, AnimSequence, nullptr, RecordingSettings);
|
|
});
|
|
|
|
FStartAnimationCB StartCallback = FStartAnimationCB::CreateLambda([SkelMeshComp, &AnimationRecorder, &CurrentTimecode, &SampleRate, ExportOptions](FFrameNumber FrameNumber)
|
|
{
|
|
if (ExportOptions->bBakeTimecode)
|
|
{
|
|
ensureMsgf(AnimationRecorder.Recorder.IsValid(), TEXT("Unable to bake timecode due to Invalid Recorder."));
|
|
|
|
if (AnimationRecorder.Recorder.IsValid())
|
|
{
|
|
// Update the time because AnimationRecorder.BeginRecording() will key the first frame.
|
|
CurrentTimecode.Time = FQualifiedFrameTime(FFrameTime(FrameNumber), SampleRate).ConvertTo(CurrentTimecode.Rate);
|
|
|
|
AnimationRecorder.Recorder->SetCurrentFrameTimeGetter([&CurrentTimecode]() -> TOptional<FQualifiedFrameTime>
|
|
{
|
|
return CurrentTimecode;
|
|
});
|
|
}
|
|
}
|
|
|
|
AnimationRecorder.BeginRecording();
|
|
SkelMeshComp->UpdateLODStatus();
|
|
});
|
|
|
|
FTickAnimationCB TickCallback = FTickAnimationCB::CreateLambda([&AnimationRecorder, &CurrentTimecode, SampleRate, ExportOptions](float DeltaTime, FFrameNumber FrameNumber)
|
|
{
|
|
// Update current timecode, which will be read during AnimationRecorder via the custom current time getter.
|
|
if (ExportOptions->bBakeTimecode)
|
|
{
|
|
CurrentTimecode.Time = FQualifiedFrameTime(FFrameTime(FrameNumber), SampleRate).ConvertTo(CurrentTimecode.Rate);
|
|
}
|
|
|
|
AnimationRecorder.Update(DeltaTime);
|
|
});
|
|
|
|
FEndAnimationCB EndCallback = FEndAnimationCB::CreateLambda([&AnimationRecorder, ExportOptions, AnimSequence, SkelMeshComp, AESP]
|
|
{
|
|
if (ExportOptions->bBakeTimecode)
|
|
{
|
|
FProcessRecordedTimeParams TimeParams;
|
|
|
|
// Use the timecode attribute names in the animation settings for consistency.
|
|
if (const UAnimationSettings* AnimationSettings = UAnimationSettings::Get())
|
|
{
|
|
TimeParams.HoursName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.HourAttributeName.ToString();
|
|
TimeParams.MinutesName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.MinuteAttributeName.ToString();
|
|
TimeParams.SecondsName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.SecondAttributeName.ToString();
|
|
TimeParams.FramesName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.FrameAttributeName.ToString();
|
|
TimeParams.SubFramesName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.SubframeAttributeName.ToString();
|
|
TimeParams.RateName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.RateAttributeName.ToString();
|
|
TimeParams.SlateName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.TakenameAttributeName.ToString();
|
|
|
|
TimeParams.Slate = GetSlateFromMovieSceneSequence(AESP.MovieSceneSequence);
|
|
}
|
|
|
|
AnimationRecorder.ProcessRecordedTimes(AnimSequence, SkelMeshComp, FTimecodeBoneMethod(), TimeParams);
|
|
}
|
|
|
|
const bool bShowAnimationAssetCreatedToast = false;
|
|
AnimationRecorder.FinishRecording(bShowAnimationAssetCreatedToast);
|
|
|
|
// Clean up the getter since it references a local variable and AnimationRecorder.Recorder is managed by a shared pointer.
|
|
if (ExportOptions->bBakeTimecode && AnimationRecorder.Recorder.IsValid())
|
|
{
|
|
AnimationRecorder.Recorder->SetCurrentFrameTimeGetter([]() -> TOptional<FQualifiedFrameTime>
|
|
{
|
|
return FApp::GetCurrentFrameTime();
|
|
});
|
|
}
|
|
});
|
|
|
|
MovieSceneToolHelpers::BakeToSkelMeshToCallbacks(AESP, SkelMeshComp, ExportOptions,
|
|
InitCallback, StartCallback, TickCallback, EndCallback);
|
|
|
|
return AnimSequence->GetDataModel()->HasBeenPopulated();
|
|
}
|
|
|
|
|
|
FSpawnableRestoreState::FSpawnableRestoreState(UMovieScene* MovieScene)
|
|
: FSpawnableRestoreState(MovieScene, nullptr)
|
|
{
|
|
|
|
}
|
|
|
|
FSpawnableRestoreState::FSpawnableRestoreState(UMovieScene* MovieScene, TSharedPtr<UE::MovieScene::FSharedPlaybackState> InSharedPlaybackState)
|
|
: bWasChanged(false)
|
|
, WeakMovieScene(MovieScene)
|
|
, SharedPlaybackState(InSharedPlaybackState)
|
|
{
|
|
auto SaveStateForSpawnable = [this](FGuid BindingGuid, ESpawnOwnership PreviousSpawnOwnership, TFunction<void(ESpawnOwnership)> SpawnOwnershipSetter)
|
|
{
|
|
// Spawnable could be in a subscene, so temporarily override it to persist throughout
|
|
SpawnOwnershipMap.Add(BindingGuid, PreviousSpawnOwnership);
|
|
|
|
SpawnOwnershipSetter(ESpawnOwnership::RootSequence);
|
|
|
|
UMovieSceneSpawnTrack* SpawnTrack = WeakMovieScene->FindTrack<UMovieSceneSpawnTrack>(BindingGuid);
|
|
|
|
if (SpawnTrack && SpawnTrack->GetAllSections().Num() > 0)
|
|
{
|
|
// Start a transaction that will be undone later for the modifications to the spawn track
|
|
if (!bWasChanged)
|
|
{
|
|
GEditor->BeginTransaction(NSLOCTEXT("MovieSceneToolHelpers", "SpwanableRestoreState", "SpawnableRestoreState"));
|
|
}
|
|
|
|
bWasChanged = true;
|
|
|
|
UMovieSceneSpawnSection* SpawnSection = Cast<UMovieSceneSpawnSection>(SpawnTrack->GetAllSections()[0]);
|
|
SpawnSection->Modify();
|
|
SpawnSection->GetChannel().Reset();
|
|
SpawnSection->GetChannel().SetDefault(true);
|
|
}
|
|
|
|
UMovieSceneBindingLifetimeTrack* BindingLifetimeTrack = WeakMovieScene->FindTrack<UMovieSceneBindingLifetimeTrack>(BindingGuid);
|
|
if (BindingLifetimeTrack && BindingLifetimeTrack->GetAllSections().Num() > 0)
|
|
{
|
|
// Start a transaction that will be undone later for the modifications to the binding lifetime track
|
|
if (!bWasChanged)
|
|
{
|
|
GEditor->BeginTransaction(NSLOCTEXT("MovieSceneToolHelpers", "SpwanableRestoreState", "SpawnableRestoreState"));
|
|
}
|
|
|
|
bWasChanged = true;
|
|
|
|
BindingLifetimeTrack->Modify();
|
|
BindingLifetimeTrack->RemoveAllAnimationData();
|
|
// Add a single infinite section
|
|
UMovieSceneSection* NewSection = NewObject<UMovieSceneSection>(BindingLifetimeTrack, UMovieSceneBindingLifetimeSection::StaticClass(), NAME_None, RF_Transactional);
|
|
check(NewSection);
|
|
NewSection->SetRange(TRange<FFrameNumber>::All());
|
|
BindingLifetimeTrack->AddSection(*NewSection);
|
|
}
|
|
};
|
|
|
|
for (int32 SpawnableIndex = 0; SpawnableIndex < WeakMovieScene->GetSpawnableCount(); ++SpawnableIndex)
|
|
{
|
|
FMovieSceneSpawnable& Spawnable = WeakMovieScene->GetSpawnable(SpawnableIndex);
|
|
|
|
SaveStateForSpawnable(Spawnable.GetGuid(), Spawnable.GetSpawnOwnership(), [&Spawnable](ESpawnOwnership SpawnOwnership) { Spawnable.SetSpawnOwnership(SpawnOwnership);});
|
|
}
|
|
|
|
TSharedPtr<UE::MovieScene::FSharedPlaybackState> SharedPlaybackStateToUse = SharedPlaybackState;
|
|
if (!SharedPlaybackStateToUse)
|
|
{
|
|
SharedPlaybackStateToUse = MovieSceneHelpers::CreateTransientSharedPlaybackState(GEditor->GetEditorWorldContext().World(), WeakMovieScene->GetTypedOuter<UMovieSceneSequence>());
|
|
}
|
|
|
|
ensure(SharedPlaybackStateToUse.IsValid());
|
|
if (UMovieSceneSequence* Sequence = WeakMovieScene->GetTypedOuter<UMovieSceneSequence>())
|
|
{
|
|
if (FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences())
|
|
{
|
|
for (FMovieSceneBindingReference& BindingReference : BindingReferences->GetAllReferences())
|
|
{
|
|
if (BindingReference.CustomBinding)
|
|
{
|
|
if (UMovieSceneSpawnableBindingBase* SpawnableBinding = BindingReference.CustomBinding->AsSpawnable(SharedPlaybackStateToUse.ToSharedRef()))
|
|
{
|
|
SaveStateForSpawnable(BindingReference.ID, SpawnableBinding->SpawnOwnership, [SpawnableBinding](ESpawnOwnership SpawnOwnership) { SpawnableBinding->SpawnOwnership = SpawnOwnership; });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bWasChanged)
|
|
{
|
|
GEditor->EndTransaction();
|
|
}
|
|
}
|
|
FSpawnableRestoreState::~FSpawnableRestoreState()
|
|
{
|
|
if (!bWasChanged || !WeakMovieScene.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Restore spawnable owners
|
|
for (int32 SpawnableIndex = 0; SpawnableIndex < WeakMovieScene->GetSpawnableCount(); ++SpawnableIndex)
|
|
{
|
|
FMovieSceneSpawnable& Spawnable = WeakMovieScene->GetSpawnable(SpawnableIndex);
|
|
if (ESpawnOwnership* SpawnOwnership = SpawnOwnershipMap.Find(Spawnable.GetGuid()))
|
|
{
|
|
Spawnable.SetSpawnOwnership(*SpawnOwnership);
|
|
}
|
|
}
|
|
|
|
TSharedPtr<UE::MovieScene::FSharedPlaybackState> SharedPlaybackStateToUse = SharedPlaybackState;
|
|
if (!SharedPlaybackStateToUse)
|
|
{
|
|
// Supporting deprecated path where we have no SharedPlaybackState
|
|
UE::MovieScene::FSharedPlaybackStateCreateParams CreateParams;
|
|
CreateParams.PlaybackContext = GEditor->GetEditorWorldContext().World();
|
|
UMovieSceneSequence* ThisSequence = WeakMovieScene->GetTypedOuter<UMovieSceneSequence>();
|
|
SharedPlaybackStateToUse = MakeShared<UE::MovieScene::FSharedPlaybackState>(*ThisSequence, CreateParams);
|
|
|
|
FMovieSceneEvaluationState State;
|
|
SharedPlaybackStateToUse->AddCapabilityRaw(&State);
|
|
State.AssignSequence(MovieSceneSequenceID::Root, *ThisSequence, SharedPlaybackStateToUse.ToSharedRef());
|
|
}
|
|
ensure(SharedPlaybackStateToUse.IsValid());
|
|
|
|
if (UMovieSceneSequence* Sequence = WeakMovieScene->GetTypedOuter<UMovieSceneSequence>())
|
|
{
|
|
if (FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences())
|
|
{
|
|
for (FMovieSceneBindingReference& BindingReference : BindingReferences->GetAllReferences())
|
|
{
|
|
if (BindingReference.CustomBinding)
|
|
{
|
|
if (UMovieSceneSpawnableBindingBase* SpawnableBinding = BindingReference.CustomBinding->AsSpawnable(SharedPlaybackStateToUse.ToSharedRef()))
|
|
{
|
|
if (ESpawnOwnership* SpawnOwnership = SpawnOwnershipMap.Find(BindingReference.ID))
|
|
{
|
|
SpawnableBinding->SpawnOwnership = *SpawnOwnership;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restore modified spawned sections
|
|
bool bOrigSquelchTransactionNotification = GEditor->bSquelchTransactionNotification;
|
|
GEditor->bSquelchTransactionNotification = true;
|
|
GEditor->UndoTransaction(false);
|
|
GEditor->bSquelchTransactionNotification = bOrigSquelchTransactionNotification;
|
|
}
|
|
|
|
|
|
void MovieSceneToolHelpers::GetParents(TArray<const UObject*>& Parents, const UObject* InObject)
|
|
{
|
|
const AActor* Actor = Cast<AActor>(InObject);
|
|
if (Actor)
|
|
{
|
|
Parents.Emplace(Actor);
|
|
const AActor* ParentActor = Actor->GetAttachParentActor();
|
|
if (ParentActor)
|
|
{
|
|
GetParents(Parents, ParentActor);
|
|
}
|
|
}
|
|
}
|
|
/** This is not that scalable moving forward with stuff like the control rig , need a better caching solution there */
|
|
bool MovieSceneToolHelpers::GetParentTM(FTransform& CurrentRefTM, const TSharedPtr<ISequencer>& Sequencer, UObject* ParentObject, FFrameTime KeyTime)
|
|
{
|
|
UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence();
|
|
if (!Sequence)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FGuid ObjectBinding = Sequencer->FindCachedObjectId(*ParentObject, Sequencer->GetFocusedTemplateID());
|
|
if (!ObjectBinding.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FMovieSceneBinding* Binding = Sequence->GetMovieScene()->FindBinding(ObjectBinding);
|
|
if (!Binding)
|
|
{
|
|
return false;
|
|
}
|
|
//TODO this doesn't handle blended sections at all
|
|
for (const UMovieSceneTrack* Track : Binding->GetTracks())
|
|
{
|
|
const UMovieScene3DTransformTrack* TransformTrack = Cast<UMovieScene3DTransformTrack>(Track);
|
|
if (!TransformTrack)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//we used to loop between sections here and only evaluate if we are in a section, this will give us wrong transfroms though
|
|
//when in between or outside of the section range. We still want to evaluate, though it is heavy.
|
|
|
|
const FMovieSceneEvaluationTrack* EvalTrack = MovieSceneToolHelpers::GetEvaluationTrack(Sequencer.Get(), TransformTrack->GetSignature());
|
|
if (EvalTrack)
|
|
{
|
|
FVector ParentKeyPos;
|
|
FRotator ParentKeyRot;
|
|
GetLocationAtTime(EvalTrack, ParentObject, KeyTime, ParentKeyPos, ParentKeyRot, Sequencer);
|
|
CurrentRefTM = FTransform(ParentKeyRot, ParentKeyPos);
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FTransform MovieSceneToolHelpers::GetRefFrameFromParents(const TSharedPtr<ISequencer>& Sequencer, const TArray<const UObject*>& Parents, FFrameTime KeyTime)
|
|
{
|
|
FTransform RefTM = FTransform::Identity;
|
|
FTransform ParentRefTM = FTransform::Identity;
|
|
|
|
for (const UObject* Object : Parents)
|
|
{
|
|
const AActor* Actor = Cast<AActor>(Object);
|
|
if (Actor != nullptr)
|
|
{
|
|
if (Actor->GetRootComponent() != nullptr && Actor->GetRootComponent()->GetAttachParent() != nullptr)
|
|
{
|
|
//Always get local ref tm since we don't know which parent is in the sequencer or not.
|
|
if (!GetParentTM(ParentRefTM, Sequencer, Actor->GetRootComponent()->GetAttachParent()->GetOwner(), KeyTime))
|
|
{
|
|
AActor* Parent = Actor->GetRootComponent()->GetAttachParent()->GetOwner();
|
|
if (Parent && Parent->GetRootComponent())
|
|
{
|
|
ParentRefTM = Parent->GetRootComponent()->GetRelativeTransform();
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
RefTM = ParentRefTM * RefTM;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const USceneComponent* SceneComponent = Cast<USceneComponent>(Object);
|
|
FTransform CurrentRefTM = FTransform::Identity;
|
|
UObject* ParentObject = SceneComponent->GetAttachParent() == SceneComponent->GetOwner()->GetRootComponent() ? static_cast<UObject*>(SceneComponent->GetOwner()) : SceneComponent->GetAttachParent();
|
|
|
|
if (SceneComponent->GetAttachParent() != nullptr)
|
|
{
|
|
if (!GetParentTM(CurrentRefTM, Sequencer, ParentObject, KeyTime))
|
|
{
|
|
CurrentRefTM = RefTM * SceneComponent->GetAttachParent()->GetRelativeTransform();
|
|
}
|
|
}
|
|
RefTM = CurrentRefTM * RefTM;
|
|
}
|
|
}
|
|
return RefTM;
|
|
}
|
|
|
|
void MovieSceneToolHelpers::GetLocationAtTime(const FMovieSceneEvaluationTrack* Track, UObject* Object, FFrameTime KeyTime, FVector& KeyPos, FRotator& KeyRot, const TSharedPtr<ISequencer>& Sequencer)
|
|
{
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// TODO: Reimplement trajectory rendering
|
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
UE_MOVIESCENE_TODO(Reimplement trajectory rendering)
|
|
}
|
|
|
|
USkeletalMeshComponent* MovieSceneToolHelpers::AcquireSkeletalMeshFromObject(UObject* BoundObject)
|
|
{
|
|
if (AActor* Actor = Cast<AActor>(BoundObject))
|
|
{
|
|
for (UActorComponent* Component : Actor->GetComponents())
|
|
{
|
|
if (USkeletalMeshComponent* SkeletalMeshComp = Cast<USkeletalMeshComponent>(Component))
|
|
{
|
|
return SkeletalMeshComp;
|
|
}
|
|
}
|
|
}
|
|
else if (USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(BoundObject))
|
|
{
|
|
if (SkeletalMeshComponent->GetSkeletalMeshAsset())
|
|
{
|
|
return SkeletalMeshComponent;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
static void GetSequenceSceneComponentWorldTransforms(USceneComponent* SceneComponent, IMovieScenePlayer* Player, UMovieSceneSequence* InSequence, FMovieSceneSequenceIDRef Template, const TArray<FFrameNumber>& Frames, TArray<FTransform>& OutTransforms)
|
|
{
|
|
|
|
// Hack: static system interrogator for now to avoid re-allocating UObjects all the time
|
|
static UE::MovieScene::FSystemInterrogator Interrogator;
|
|
Interrogator.Reset();
|
|
|
|
Interrogator.ImportTransformHierarchy(SceneComponent, Player, Template);
|
|
|
|
if (Frames[0] < Frames[Frames.Num() - 1])
|
|
{
|
|
for (const FFrameNumber& FrameNumber : Frames)
|
|
{
|
|
const FFrameTime FrameTime(FrameNumber);
|
|
Interrogator.AddInterrogation(FrameTime);
|
|
}
|
|
Interrogator.Update();
|
|
|
|
Interrogator.QueryWorldSpaceTransforms(SceneComponent, OutTransforms);
|
|
}
|
|
else //time is backward we need to do swap things around
|
|
{
|
|
for (int32 Index = Frames.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
const FFrameTime FrameTime(Frames[Index]);
|
|
Interrogator.AddInterrogation(FrameTime);
|
|
}
|
|
Interrogator.Update();
|
|
|
|
TArray<FTransform> WorldTransforms;
|
|
Interrogator.QueryWorldSpaceTransforms(SceneComponent, WorldTransforms);
|
|
OutTransforms.SetNum(0);
|
|
OutTransforms.Reserve(WorldTransforms.Num());
|
|
for (int32 Index2 = WorldTransforms.Num() - 1; Index2 >= 0; --Index2)
|
|
{
|
|
OutTransforms.Add(WorldTransforms[Index2]);
|
|
}
|
|
}
|
|
Interrogator.Reset();
|
|
}
|
|
|
|
static void GetSequencerActorWorldTransforms(IMovieScenePlayer* Player, FMovieSceneSequenceTransform RootToLocalTransform, UMovieSceneSequence* InSequence, FMovieSceneSequenceIDRef Template, const FActorForWorldTransforms& ActorSelection, const TArray<FFrameNumber>& Frames, TArray<FTransform>& OutTransforms)
|
|
{
|
|
USceneComponent* SceneComponent = nullptr;
|
|
AActor* Actor = ActorSelection.Actor.Get();
|
|
if (Actor)
|
|
{
|
|
SceneComponent = Actor->GetRootComponent();
|
|
}
|
|
else
|
|
{
|
|
SceneComponent = ActorSelection.Component.IsValid() ? Cast<USceneComponent>(ActorSelection.Component.Get()) : nullptr;
|
|
if (SceneComponent)
|
|
{
|
|
Actor = SceneComponent->GetTypedOuter<AActor>();
|
|
}
|
|
}
|
|
|
|
if(Actor && SceneComponent)
|
|
{
|
|
USkeletalMeshComponent* SkelMeshComp = Cast<USkeletalMeshComponent>(SceneComponent);
|
|
|
|
if (!SkelMeshComp)
|
|
{
|
|
SkelMeshComp = MovieSceneToolHelpers::AcquireSkeletalMeshFromObject(Actor);
|
|
}
|
|
|
|
if (UMovieScene* MovieScene = InSequence->GetMovieScene())
|
|
{
|
|
OutTransforms.SetNum(Frames.Num());
|
|
|
|
FFrameRate TickResolution = MovieScene->GetTickResolution();
|
|
FFrameRate DisplayRate = MovieScene->GetDisplayRate();
|
|
const TArray<IMovieSceneToolsAnimationBakeHelper*>& BakeHelpers = FMovieSceneToolsModule::Get().GetAnimationBakeHelpers();
|
|
|
|
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
|
|
{
|
|
if (BakeHelper)
|
|
{
|
|
BakeHelper->StartBaking(MovieScene);
|
|
}
|
|
}
|
|
|
|
for (int32 Index = 0; Index < Frames.Num(); ++Index)
|
|
{
|
|
FFrameNumber FrameNumber = Frames[Index];
|
|
FFrameTime GlobalTime = RootToLocalTransform.Inverse().TryTransformTime(FrameNumber).Get(FrameNumber); //player evals in root time so need to go back to it.
|
|
|
|
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
|
|
{
|
|
if (BakeHelper)
|
|
{
|
|
BakeHelper->PreEvaluation(MovieScene, FrameNumber);
|
|
}
|
|
}
|
|
|
|
FMovieSceneContext Context = FMovieSceneContext(FMovieSceneEvaluationRange(GlobalTime, TickResolution), Player->GetPlaybackStatus()).SetHasJumped(true);
|
|
if (Index == 0) // similar with baking first time in we need to evaluate twice (think due to double buffering that happens with skel mesh components).
|
|
{
|
|
Player->GetEvaluationTemplate().EvaluateSynchronousBlocking(Context);
|
|
}
|
|
Player->GetEvaluationTemplate().EvaluateSynchronousBlocking(Context);
|
|
|
|
|
|
const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(Actor->GetWorld());
|
|
Controller.EvaluateAllConstraints();
|
|
|
|
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
|
|
{
|
|
if (BakeHelper)
|
|
{
|
|
BakeHelper->PostEvaluation(MovieScene, FrameNumber);
|
|
}
|
|
}
|
|
|
|
AActor* Parent = ActorSelection.Actor.Get();
|
|
while (Parent)
|
|
{
|
|
TArray<USkeletalMeshComponent*> MeshComps;
|
|
Parent->GetComponents(MeshComps, true);
|
|
|
|
for (USkeletalMeshComponent* MeshComp : MeshComps)
|
|
{
|
|
MeshComp->TickAnimation(0.03f, false);
|
|
MeshComp->RefreshBoneTransforms();
|
|
MeshComp->RefreshFollowerComponents();
|
|
MeshComp->UpdateComponentToWorld();
|
|
MeshComp->FinalizeBoneTransform();
|
|
MeshComp->MarkRenderTransformDirty();
|
|
MeshComp->MarkRenderDynamicDataDirty();
|
|
}
|
|
|
|
Parent = Parent->GetAttachParentActor();
|
|
}
|
|
|
|
OutTransforms[Index] = (SkelMeshComp && ActorSelection.SocketName != NAME_None)
|
|
? SkelMeshComp->GetSocketTransform(ActorSelection.SocketName)
|
|
: SceneComponent->GetComponentToWorld();
|
|
|
|
}
|
|
|
|
for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers)
|
|
{
|
|
if (BakeHelper)
|
|
{
|
|
BakeHelper->StopBaking(MovieScene);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void GetNonSequencerActorWorldTransforms(IMovieScenePlayer* Player, FMovieSceneSequenceTransform RootToLocalTransform, UMovieSceneSequence* InSequence, FMovieSceneSequenceIDRef Template, const FActorForWorldTransforms& ActorSelection, const TArray<FFrameNumber>& Frames, TArray<FTransform>& OutTransforms)
|
|
{
|
|
FName SocketName = ActorSelection.SocketName;
|
|
AActor* Actor = ActorSelection.Actor.Get();
|
|
FActorForWorldTransforms NewActorSelection = ActorSelection;
|
|
FTransform WorldTransform = FTransform::Identity;
|
|
if (Actor)
|
|
{
|
|
do
|
|
{
|
|
FGuid ActorHandle = GetHandleToObject(Actor, InSequence, Player, Template,false);
|
|
if (ActorHandle.IsValid() && Frames.Num() > 0)
|
|
{
|
|
if (InSequence->GetMovieScene()->FindTrack<UMovieScene3DTransformTrack>(ActorHandle))
|
|
{
|
|
GetSequencerActorWorldTransforms(Player, RootToLocalTransform, InSequence, Template, NewActorSelection, Frames, OutTransforms);
|
|
for (FTransform& OutTransform : OutTransforms)
|
|
{
|
|
OutTransform = WorldTransform * OutTransform;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (Actor->GetRootComponent()->DoesSocketExist(SocketName))
|
|
{
|
|
WorldTransform = WorldTransform * Actor->GetRootComponent()->GetSocketTransform(SocketName);
|
|
}
|
|
else
|
|
{
|
|
WorldTransform = WorldTransform * Actor->GetRootComponent()->GetRelativeTransform();
|
|
}
|
|
SocketName = Actor->GetAttachParentSocketName();
|
|
Actor = Actor->GetAttachParentActor();
|
|
NewActorSelection.Actor = Actor;
|
|
if (Actor)
|
|
{
|
|
NewActorSelection.SocketName = SocketName;
|
|
}
|
|
} while (Actor);
|
|
//if we get to here world transform is what we want
|
|
}
|
|
int32 NumFrames = Frames.Num() > 0 ? Frames.Num() : 1; //if no frames passed in we actually have one
|
|
OutTransforms.SetNumUninitialized(NumFrames);
|
|
for (FTransform& OutTransform : OutTransforms)
|
|
{
|
|
OutTransform = WorldTransform;
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::GetActorWorldTransforms(ISequencer* Sequencer, const FActorForWorldTransforms& ActorSelection, const TArray<FFrameNumber>& Frames, TArray<FTransform>& OutWorldTransforms)
|
|
{
|
|
FMovieSceneSequenceIDRef Template = Sequencer->GetFocusedTemplateID();
|
|
const bool bAvoidEvaluates = (Frames.Num() == 0) || (Frames.Num() == 1 && Frames[0] == Sequencer->GetLocalTime().Time.RoundToFrame());
|
|
FGuid ObjectHandle = Sequencer->GetHandleToObject(ActorSelection.Actor.Get(), false);
|
|
if (!bAvoidEvaluates && ObjectHandle.IsValid())
|
|
{
|
|
if (Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->FindTrack<UMovieScene3DTransformTrack>(ObjectHandle))
|
|
{
|
|
GetSequencerActorWorldTransforms(Sequencer, Sequencer->GetFocusedMovieSceneSequenceTransform(), Sequencer->GetFocusedMovieSceneSequence(), Template, ActorSelection, Frames, OutWorldTransforms);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ObjectHandle = Sequencer->GetHandleToObject(ActorSelection.Component.Get(), false);
|
|
if (!bAvoidEvaluates && ObjectHandle.IsValid())
|
|
{
|
|
if (Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->FindTrack<UMovieScene3DTransformTrack>(ObjectHandle))
|
|
{
|
|
GetSequencerActorWorldTransforms(Sequencer, Sequencer->GetFocusedMovieSceneSequenceTransform(), Sequencer->GetFocusedMovieSceneSequence(), Template, ActorSelection, Frames, OutWorldTransforms);
|
|
return;
|
|
}
|
|
}
|
|
if (bAvoidEvaluates)
|
|
{
|
|
TArray<FFrameNumber> NoFrame; //this will make sure we don't evaluate
|
|
GetNonSequencerActorWorldTransforms(Sequencer, Sequencer->GetFocusedMovieSceneSequenceTransform(), Sequencer->GetFocusedMovieSceneSequence(), Template, ActorSelection, NoFrame, OutWorldTransforms);
|
|
}
|
|
else
|
|
{
|
|
GetNonSequencerActorWorldTransforms(Sequencer, Sequencer->GetFocusedMovieSceneSequenceTransform(), Sequencer->GetFocusedMovieSceneSequence(), Template, ActorSelection, Frames, OutWorldTransforms);
|
|
}
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::IsValidAsset(UMovieSceneSequence* Sequence, const FAssetData& InAssetData)
|
|
{
|
|
if (!Sequence)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FAssetReferenceFilterContext AssetReferenceFilterContext;
|
|
AssetReferenceFilterContext.AddReferencingAsset(FAssetData(Sequence));
|
|
|
|
TSharedPtr<IAssetReferenceFilter> AssetReferenceFilter = GEditor->MakeAssetReferenceFilter(AssetReferenceFilterContext);
|
|
|
|
FText FailureReason;
|
|
if (AssetReferenceFilter.IsValid() && !AssetReferenceFilter->PassesFilter(InAssetData, &FailureReason))
|
|
{
|
|
UE_LOG(LogMovieScene, Warning, TEXT("%s cannot reference because: %s"), *GetNameSafe(Sequence), *FailureReason.ToString());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData<FMovieSceneFloatValue>& ChannelData, FFrameNumber Time, float Value, const EMovieSceneKeyInterpolation Interpolation)
|
|
{
|
|
int32 ExistingIndex = ChannelData.FindKey(Time);
|
|
if (ExistingIndex != INDEX_NONE)
|
|
{
|
|
FMovieSceneFloatValue& FloatValue = ChannelData.GetValues()[ExistingIndex]; //-V758
|
|
FloatValue.Value = Value;
|
|
}
|
|
else
|
|
{
|
|
FMovieSceneFloatValue NewKey(Value);
|
|
ERichCurveTangentWeightMode WeightedMode = RCTWM_WeightedNone;
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic;
|
|
NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto;
|
|
NewKey.Tangent.ArriveTangent = 0.0f;
|
|
NewKey.Tangent.LeaveTangent = 0.0f;
|
|
NewKey.Tangent.TangentWeightMode = WeightedMode;
|
|
NewKey.Tangent.ArriveTangentWeight = 0.0f;
|
|
NewKey.Tangent.LeaveTangentWeight = 0.0f;
|
|
|
|
switch (Interpolation)
|
|
{
|
|
case EMovieSceneKeyInterpolation::SmartAuto:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic;
|
|
NewKey.TangentMode = ERichCurveTangentMode::RCTM_SmartAuto;
|
|
}
|
|
break;
|
|
case EMovieSceneKeyInterpolation::Auto:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic;
|
|
NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto;
|
|
}
|
|
break;
|
|
case EMovieSceneKeyInterpolation::User:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic;
|
|
NewKey.TangentMode = ERichCurveTangentMode::RCTM_User;
|
|
}
|
|
break;
|
|
case EMovieSceneKeyInterpolation::Break:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic;
|
|
NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto;
|
|
}
|
|
break;
|
|
|
|
case EMovieSceneKeyInterpolation::Linear:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Linear;
|
|
}
|
|
break;
|
|
|
|
case EMovieSceneKeyInterpolation::Constant:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Constant;
|
|
}
|
|
break;
|
|
|
|
}
|
|
ChannelData.AddKey(Time, NewKey);
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData<FMovieSceneFloatValue>& ChannelData, FFrameNumber Time, const FMovieSceneFloatValue& Value)
|
|
{
|
|
int32 ExistingIndex = ChannelData.FindKey(Time);
|
|
if (ExistingIndex != INDEX_NONE)
|
|
{
|
|
FMovieSceneFloatValue& FloatValue = ChannelData.GetValues()[ExistingIndex]; //-V758
|
|
FloatValue.Value = Value.Value;
|
|
}
|
|
else
|
|
{
|
|
ChannelData.AddKey(Time, Value);
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData<FMovieSceneFloatValue>& ChannelData, FFrameNumber Time, float Value,
|
|
float ArriveTangent, float LeaveTangent, ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode,
|
|
FFrameRate FrameRate, ERichCurveTangentWeightMode WeightedMode, float ArriveTangentWeight, float LeaveTangentWeight)
|
|
{
|
|
FMovieSceneFloatValue NewKey(Value);
|
|
|
|
NewKey.InterpMode = InterpMode;
|
|
NewKey.TangentMode = TangentMode;
|
|
NewKey.Tangent.ArriveTangent = ArriveTangent / FrameRate.AsDecimal();
|
|
NewKey.Tangent.LeaveTangent = LeaveTangent / FrameRate.AsDecimal();
|
|
NewKey.Tangent.TangentWeightMode = WeightedMode;
|
|
NewKey.Tangent.ArriveTangentWeight = ArriveTangentWeight;
|
|
NewKey.Tangent.LeaveTangentWeight = LeaveTangentWeight;
|
|
ChannelData.AddKey(Time, NewKey);
|
|
MovieSceneToolHelpers::SetOrAddKey(ChannelData, Time, NewKey);
|
|
}
|
|
|
|
void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData<FMovieSceneDoubleValue>& ChannelData, FFrameNumber Time, double Value,
|
|
float ArriveTangent, float LeaveTangent, ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode,
|
|
FFrameRate FrameRate, ERichCurveTangentWeightMode WeightedMode, float ArriveTangentWeight, float LeaveTangentWeight)
|
|
{
|
|
FMovieSceneDoubleValue NewKey(Value);
|
|
NewKey.InterpMode = InterpMode;
|
|
NewKey.TangentMode = TangentMode;
|
|
NewKey.Tangent.ArriveTangent = ArriveTangent / FrameRate.AsDecimal();
|
|
NewKey.Tangent.LeaveTangent = LeaveTangent / FrameRate.AsDecimal();
|
|
NewKey.Tangent.TangentWeightMode = WeightedMode;
|
|
NewKey.Tangent.ArriveTangentWeight = ArriveTangentWeight;
|
|
NewKey.Tangent.LeaveTangentWeight = LeaveTangentWeight;
|
|
MovieSceneToolHelpers::SetOrAddKey(ChannelData, Time, NewKey);
|
|
}
|
|
|
|
void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData<FMovieSceneDoubleValue>& ChannelData, FFrameNumber Time, FMovieSceneDoubleValue Value)
|
|
{
|
|
int32 ExistingIndex = ChannelData.FindKey(Time);
|
|
if (ExistingIndex != INDEX_NONE)
|
|
{
|
|
FMovieSceneDoubleValue& DoubleValue = ChannelData.GetValues()[ExistingIndex]; //-V758
|
|
DoubleValue.Value = Value.Value;
|
|
}
|
|
else
|
|
{
|
|
ChannelData.AddKey(Time, Value);
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData<FMovieSceneDoubleValue>& ChannelData, FFrameNumber Time, double Value, const EMovieSceneKeyInterpolation Interpolation)
|
|
{
|
|
int32 ExistingIndex = ChannelData.FindKey(Time);
|
|
if (ExistingIndex != INDEX_NONE)
|
|
{
|
|
FMovieSceneDoubleValue& DoubleValue = ChannelData.GetValues()[ExistingIndex]; //-V758
|
|
DoubleValue.Value = Value;
|
|
}
|
|
else
|
|
{
|
|
FMovieSceneDoubleValue NewKey(Value);
|
|
ERichCurveTangentWeightMode WeightedMode = RCTWM_WeightedNone;
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic;
|
|
NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto;
|
|
NewKey.Tangent.ArriveTangent = 0.0f;
|
|
NewKey.Tangent.LeaveTangent = 0.0f;
|
|
NewKey.Tangent.TangentWeightMode = WeightedMode;
|
|
NewKey.Tangent.ArriveTangentWeight = 0.0f;
|
|
NewKey.Tangent.LeaveTangentWeight = 0.0f;
|
|
|
|
switch (Interpolation)
|
|
{
|
|
case EMovieSceneKeyInterpolation::SmartAuto:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic;
|
|
NewKey.TangentMode = ERichCurveTangentMode::RCTM_SmartAuto;
|
|
}
|
|
break;
|
|
case EMovieSceneKeyInterpolation::Auto:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic;
|
|
NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto;
|
|
}
|
|
break;
|
|
case EMovieSceneKeyInterpolation::User:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic;
|
|
NewKey.TangentMode = ERichCurveTangentMode::RCTM_User;
|
|
}
|
|
break;
|
|
case EMovieSceneKeyInterpolation::Break:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic;
|
|
NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto;
|
|
}
|
|
break;
|
|
|
|
case EMovieSceneKeyInterpolation::Linear:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Linear;
|
|
}
|
|
break;
|
|
|
|
case EMovieSceneKeyInterpolation::Constant:
|
|
{
|
|
NewKey.InterpMode = ERichCurveInterpMode::RCIM_Constant;
|
|
}
|
|
break;
|
|
|
|
}
|
|
ChannelData.AddKey(Time, NewKey);
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::GetActorWorldTransforms(IMovieScenePlayer* Player, UMovieSceneSequence* InSequence, FMovieSceneSequenceIDRef Template, const FActorForWorldTransforms& ActorSelection, const TArray<FFrameNumber>& Frames, TArray<FTransform>& OutWorldTransforms)
|
|
{
|
|
FGuid ActorHandle = GetHandleToObject(ActorSelection.Actor.Get(), InSequence, Player, Template,false);
|
|
FGuid ComponentHandle = GetHandleToObject(ActorSelection.Component.Get(), InSequence, Player, Template,false);
|
|
FMovieSceneSequenceTransform RootToLocalTransform;
|
|
if (ActorHandle.IsValid() || ComponentHandle.IsValid())
|
|
{
|
|
//we can have handles but if they don't have a transform track the interrogator will return identity
|
|
bool bHaveTransformTrack = false;
|
|
if (ActorHandle.IsValid())
|
|
{
|
|
if (InSequence->GetMovieScene()->FindTrack<UMovieScene3DTransformTrack>(ActorHandle))
|
|
{
|
|
bHaveTransformTrack = true;
|
|
}
|
|
}
|
|
if (ComponentHandle.IsValid())
|
|
{
|
|
if (InSequence->GetMovieScene()->FindTrack<UMovieScene3DTransformTrack>(ComponentHandle))
|
|
{
|
|
bHaveTransformTrack = true;
|
|
}
|
|
}
|
|
if (bHaveTransformTrack)
|
|
{
|
|
GetSequencerActorWorldTransforms(Player, RootToLocalTransform,InSequence, Template, ActorSelection, Frames, OutWorldTransforms);
|
|
}
|
|
else
|
|
{
|
|
GetNonSequencerActorWorldTransforms(Player, RootToLocalTransform, InSequence, Template, ActorSelection, Frames, OutWorldTransforms);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetNonSequencerActorWorldTransforms(Player, RootToLocalTransform, InSequence, Template, ActorSelection, Frames, OutWorldTransforms);
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::GetActorParents(const FActorForWorldTransforms& ActorSelection,
|
|
TArray<FActorForWorldTransforms>& OutParentActors)
|
|
{
|
|
if (ActorSelection.Actor.IsValid())
|
|
{
|
|
//has component so parent is next component
|
|
if (ActorSelection.Component.IsValid() && ActorSelection.Component->GetAttachParent())
|
|
{
|
|
FActorForWorldTransforms OutParentActor;
|
|
OutParentActor.Actor = ActorSelection.Actor;
|
|
OutParentActor.Component = ActorSelection.Component->GetAttachParent();
|
|
OutParentActors.AddUnique(OutParentActor);
|
|
GetActorParents(OutParentActor, OutParentActors);
|
|
}
|
|
else if (AActor* ParentActor = ActorSelection.Actor->GetAttachParentActor())
|
|
{
|
|
FActorForWorldTransforms OutParentActor;
|
|
OutParentActor.Actor = ParentActor;
|
|
OutParentActors.AddUnique(OutParentActor);
|
|
GetActorParents(OutParentActor, OutParentActors);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::GetActorParentsWithAttachments(ISequencer* Sequencer, const FActorForWorldTransforms& ActorSelection,
|
|
TArray<FActorForWorldTransforms>& OutParentActors)
|
|
{
|
|
if (Sequencer == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UMovieScene* FocusedMovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene();
|
|
if (!FocusedMovieScene)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ActorSelection.Actor.IsValid())
|
|
{
|
|
FGuid Guid;
|
|
if (ActorSelection.Component.Get())
|
|
{
|
|
Guid = Sequencer->GetHandleToObject(ActorSelection.Component.Get(), false);
|
|
}
|
|
if (!Guid.IsValid())
|
|
{
|
|
Guid = Sequencer->GetHandleToObject(ActorSelection.Actor.Get(), false);
|
|
}
|
|
if (Guid.IsValid())
|
|
{
|
|
for (UMovieSceneTrack* Track : FocusedMovieScene->FindTracks(UMovieScene3DConstraintTrack::StaticClass(), Guid))
|
|
{
|
|
if (UMovieScene3DConstraintTrack* ConstraintTrack = Cast<UMovieScene3DConstraintTrack>(Track))
|
|
{
|
|
for (UMovieSceneSection* ConstraintSection : ConstraintTrack->GetAllSections())
|
|
{
|
|
FMovieSceneObjectBindingID ConstraintBindingID = (Cast<UMovieScene3DConstraintSection>(ConstraintSection))->GetConstraintBindingID();
|
|
if (auto BoundObjectsView = ConstraintBindingID.ResolveBoundObjects(Sequencer->GetFocusedTemplateID(), *Sequencer); BoundObjectsView.Num())
|
|
{
|
|
TWeakObjectPtr<> ParentObject = BoundObjectsView[0];
|
|
FActorForWorldTransforms OutParentActor;
|
|
OutParentActor.Actor = Cast<AActor>(ParentObject.Get());;
|
|
OutParentActors.AddUnique(OutParentActor);
|
|
GetActorParents(OutParentActor, OutParentActors);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//has component so parent is next component
|
|
if (ActorSelection.Component.IsValid() && ActorSelection.Component->GetAttachParent())
|
|
{
|
|
FActorForWorldTransforms OutParentActor;
|
|
OutParentActor.Actor = ActorSelection.Actor;
|
|
OutParentActor.Component = ActorSelection.Component->GetAttachParent();
|
|
OutParentActors.AddUnique(OutParentActor);
|
|
GetActorParents(OutParentActor, OutParentActors);
|
|
}
|
|
else if (AActor* ParentActor = ActorSelection.Actor->GetAttachParentActor())
|
|
{
|
|
FActorForWorldTransforms OutParentActor;
|
|
OutParentActor.Actor = ParentActor;
|
|
OutParentActors.AddUnique(OutParentActor);
|
|
GetActorParents(OutParentActor, OutParentActors);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::GetActorsAndParentsKeyFrames(ISequencer* Sequencer, const FActorForWorldTransforms& InActor,
|
|
const FFrameNumber& StartFrame, const FFrameNumber& EndFrame, TSortedMap<FFrameNumber, FFrameNumber>& OutFrameMap)
|
|
{
|
|
TArray<FFrameNumber> FramesToUse;
|
|
TArray<FActorForWorldTransforms> ParentActors;
|
|
ParentActors.Add(InActor);
|
|
GetActorParentsWithAttachments(Sequencer, InActor, ParentActors);
|
|
|
|
for (FActorForWorldTransforms& Actor : ParentActors)
|
|
{
|
|
FGuid Guid;
|
|
if (Actor.Component.Get())
|
|
{
|
|
Guid = Sequencer->GetHandleToObject(Actor.Component.Get(), false);
|
|
}
|
|
if (!Guid.IsValid())
|
|
{
|
|
Guid = Sequencer->GetHandleToObject(Actor.Actor.Get(), false);
|
|
}
|
|
if (Guid.IsValid())
|
|
{
|
|
if (UMovieScene3DTransformSection* TransformSection = MovieSceneToolHelpers::GetTransformSection(Sequencer, Guid))
|
|
{
|
|
TArrayView<FMovieSceneDoubleChannel*> Channels = TransformSection->GetChannelProxy().GetChannels<FMovieSceneDoubleChannel>();
|
|
TArray<FFrameNumber> TransformFrameTimes = FMovieSceneConstraintChannelHelper::GetTransformTimes(
|
|
Channels, StartFrame, EndFrame);
|
|
for (FFrameNumber& FrameNumber : TransformFrameTimes)
|
|
{
|
|
OutFrameMap.Add(FrameNumber,FrameNumber);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::CalculateFramesBetween(
|
|
const UMovieScene* MovieScene,
|
|
FFrameNumber StartFrame,
|
|
FFrameNumber EndFrame,
|
|
int32 FrameInc,
|
|
TArray<FFrameNumber>& OutFrames)
|
|
{
|
|
const bool bReverse = StartFrame > EndFrame;
|
|
if (bReverse)
|
|
{
|
|
Swap(StartFrame, EndFrame);
|
|
}
|
|
|
|
//if the end frame is the last frame, we move it back one tick otherwise it's possible when evaluating it won't evaluate.
|
|
if (EndFrame == MovieScene->GetPlaybackRange().GetUpperBoundValue())
|
|
{
|
|
--EndFrame;
|
|
}
|
|
const FFrameRate TickResolution = MovieScene->GetTickResolution();
|
|
const FFrameRate DisplayResolution = MovieScene->GetDisplayRate();
|
|
|
|
const FFrameNumber StartTimeInDisplay = FFrameRate::TransformTime(FFrameTime(StartFrame), TickResolution, DisplayResolution).FloorToFrame();
|
|
const FFrameNumber EndTimeInDisplay = FFrameRate::TransformTime(FFrameTime(EndFrame), TickResolution, DisplayResolution).CeilToFrame();
|
|
|
|
OutFrames.Reserve(EndTimeInDisplay.Value - StartTimeInDisplay.Value + 1);
|
|
|
|
if (bReverse)
|
|
{
|
|
int32 FrameIndex = 0;
|
|
for (FFrameNumber DisplayFrameNumber = EndTimeInDisplay; DisplayFrameNumber >= StartTimeInDisplay; --DisplayFrameNumber)
|
|
{
|
|
if (FrameIndex % FrameInc == 0)
|
|
{
|
|
FFrameNumber TickFrameNumber = FFrameRate::TransformTime(FFrameTime(DisplayFrameNumber), DisplayResolution, TickResolution).FrameNumber;
|
|
OutFrames.Add(TickFrameNumber);
|
|
}
|
|
++FrameIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 FrameIndex = 0;
|
|
for (FFrameNumber DisplayFrameNumber = StartTimeInDisplay; DisplayFrameNumber <= EndTimeInDisplay; ++DisplayFrameNumber)
|
|
{
|
|
if (FrameIndex % FrameInc == 0)
|
|
{
|
|
FFrameNumber TickFrameNumber = FFrameRate::TransformTime(FFrameTime(DisplayFrameNumber), DisplayResolution, TickResolution).FrameNumber;
|
|
OutFrames.Add(TickFrameNumber);
|
|
}
|
|
++FrameIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::OptimizeSection(const FKeyDataOptimizationParams& InParams, UMovieSceneSection* InSection)
|
|
{
|
|
TArrayView<FMovieSceneFloatChannel*> FloatChannels = InSection->GetChannelProxy().GetChannels<FMovieSceneFloatChannel>();
|
|
for (FMovieSceneFloatChannel* Channel : FloatChannels)
|
|
{
|
|
Channel->Optimize(InParams);
|
|
}
|
|
TArrayView<FMovieSceneDoubleChannel*> DoubleChannels = InSection->GetChannelProxy().GetChannels<FMovieSceneDoubleChannel>();
|
|
for (FMovieSceneDoubleChannel* Channel : DoubleChannels)
|
|
{
|
|
Channel->Optimize(InParams);
|
|
}
|
|
return true;
|
|
}
|
|
void MovieSceneToolHelpers::SplitSectionsByBlendType(EMovieSceneBlendType BlendType, const TArray<UMovieSceneSection*>& InSections, TArray<UMovieSceneSection*>& Sections, TArray<UMovieSceneSection*>& OutBlendSections)
|
|
{
|
|
for (UMovieSceneSection* InSection : InSections)
|
|
{
|
|
if (InSection &&
|
|
InSection->IsActive() &&
|
|
InSection->GetBlendType().IsValid())
|
|
{
|
|
if (InSection->GetBlendType().Get() == BlendType)
|
|
{
|
|
OutBlendSections.Add(InSection);
|
|
}
|
|
else
|
|
{
|
|
Sections.Add(InSection);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::CollapseSection(TSharedPtr<ISequencer>& SequencerPtr, UMovieSceneTrack* OwnerTrack, TArray<UMovieSceneSection*> Sections,
|
|
const FBakingAnimationKeySettings& InSettings)
|
|
{
|
|
if (SequencerPtr.IsValid() && Sections.Num() > 1 && OwnerTrack)
|
|
{
|
|
TRange<FFrameNumber> Range(InSettings.StartFrame, InSettings.EndFrame);
|
|
//we get the first absolute section or if no absolute sections, first additive,
|
|
// that's the one we collapse onto
|
|
UMovieSceneSection* BaseSection = nullptr;
|
|
|
|
FScopedTransaction Transaction(NSLOCTEXT("MovieSceneTools", "CollapseAllSections", "Collapse All Sections"));
|
|
bool bTrackModified = false;
|
|
for (int32 SectionIndex = Sections.Num() - 1; SectionIndex > 0; --SectionIndex)
|
|
{
|
|
BaseSection = Sections[SectionIndex - 1];
|
|
UMovieSceneSection* Section = Sections[SectionIndex];
|
|
if (BaseSection && Section && BaseSection->GetBlendType().IsValid() && Section->GetBlendType().IsValid())
|
|
{
|
|
if (bTrackModified == false)
|
|
{
|
|
OwnerTrack->Modify();
|
|
bTrackModified = true;
|
|
}
|
|
BaseSection->Modify();
|
|
TArray<UMovieSceneSection*> TrackSections = OwnerTrack->GetAllSections();
|
|
if (Section->IsActive())//active sections merge them
|
|
{
|
|
TArrayView<FMovieSceneFloatChannel*> BaseFloatChannels = BaseSection->GetChannelProxy().GetChannels<FMovieSceneFloatChannel>();
|
|
TArrayView<FMovieSceneDoubleChannel*> BaseDoubleChannels = BaseSection->GetChannelProxy().GetChannels<FMovieSceneDoubleChannel>();
|
|
if (BaseDoubleChannels.Num() > 0)
|
|
{
|
|
const int StartIndex = 0;
|
|
const int EndIndex = BaseDoubleChannels.Num() - 1;
|
|
MovieSceneToolHelpers::MergeSections<FMovieSceneDoubleChannel>(BaseSection,
|
|
Section, StartIndex, EndIndex, Range, TrackSections);
|
|
|
|
}
|
|
else if (BaseFloatChannels.Num() > 0)
|
|
{
|
|
const int StartIndex = 0;
|
|
const int EndIndex = BaseFloatChannels.Num() - 1;
|
|
MovieSceneToolHelpers::MergeSections<FMovieSceneFloatChannel>(BaseSection,
|
|
Section,StartIndex, EndIndex, Range, TrackSections);
|
|
}
|
|
}
|
|
|
|
//if merging override onto an additive we change it to an additive
|
|
if (Section->GetBlendType().Get() == EMovieSceneBlendType::Override &&
|
|
(BaseSection->GetBlendType().Get() == EMovieSceneBlendType::Additive))
|
|
{
|
|
BaseSection->SetBlendType(EMovieSceneBlendType::Override);
|
|
}
|
|
TArray<UMovieSceneSection*> AllSections = OwnerTrack->GetAllSections();
|
|
int32 TrackSectionIndex = INDEX_NONE;
|
|
if (AllSections.Find(Section, TrackSectionIndex))
|
|
{
|
|
if (TrackSectionIndex != INDEX_NONE)
|
|
{
|
|
OwnerTrack->RemoveSectionAt(TrackSectionIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (InSettings.bReduceKeys && BaseSection)
|
|
{
|
|
FKeyDataOptimizationParams Params;
|
|
Params.bAutoSetInterpolation = true;
|
|
Params.Tolerance = InSettings.Tolerance;
|
|
FMovieSceneChannelProxy& ChannelProxy = BaseSection->GetChannelProxy();
|
|
TArrayView<FMovieSceneFloatChannel*> FloatChannels = ChannelProxy.GetChannels<FMovieSceneFloatChannel>();
|
|
|
|
for (FMovieSceneFloatChannel* Channel : FloatChannels)
|
|
{
|
|
Channel->Optimize(Params); //should also auto tangent
|
|
}
|
|
}
|
|
//reset everything back
|
|
SequencerPtr->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
|
|
}
|
|
return true;
|
|
|
|
}
|
|
|
|
UMovieScene3DTransformSection* MovieSceneToolHelpers::GetTransformSection(
|
|
const ISequencer* InSequencer,
|
|
const FGuid& InGuid,
|
|
const FTransform& InDefaultTransform)
|
|
{
|
|
if (!InSequencer || !InSequencer->GetFocusedMovieSceneSequence())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UMovieScene* MovieScene = InSequencer->GetFocusedMovieSceneSequence()->GetMovieScene();
|
|
if (!MovieScene)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UMovieScene3DTransformTrack* TransformTrack = MovieScene->FindTrack<UMovieScene3DTransformTrack>(InGuid);
|
|
if (!TransformTrack)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UMovieScene3DTransformSection* TransformSection =
|
|
Cast<UMovieScene3DTransformSection>(TransformTrack->GetSectionToKey());
|
|
if (TransformSection)
|
|
{
|
|
return TransformSection;
|
|
}
|
|
|
|
TransformSection = TransformTrack->GetAllSections().Num() > 0 ? Cast<UMovieScene3DTransformSection>(TransformTrack->GetAllSections()[0]) : nullptr;
|
|
if (TransformSection)
|
|
{
|
|
return TransformSection;
|
|
}
|
|
|
|
//there really should be a section to key or at least one section, if not then we go through creating it.
|
|
TransformTrack->Modify();
|
|
|
|
static constexpr FFrameNumber Frame0;
|
|
bool bSectionAdded = false;
|
|
TransformSection =
|
|
Cast<UMovieScene3DTransformSection>(TransformTrack->FindOrAddSection(Frame0, bSectionAdded));
|
|
if (!TransformSection)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
TransformSection->Modify();
|
|
if (bSectionAdded)
|
|
{
|
|
TransformSection->SetRange(TRange<FFrameNumber>::All());
|
|
|
|
const FVector Location0 = InDefaultTransform.GetLocation();
|
|
const FRotator Rotation0 = InDefaultTransform.GetRotation().Rotator();
|
|
const FVector Scale3D0 = InDefaultTransform.GetScale3D();
|
|
|
|
FMovieSceneChannelProxy& SectionChannelProxy = TransformSection->GetChannelProxy();
|
|
TMovieSceneChannelHandle<FMovieSceneDoubleChannel> DoubleChannels[] = {
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.Z"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.Z"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.Z")
|
|
};
|
|
|
|
if (DoubleChannels[0].Get())
|
|
{
|
|
DoubleChannels[0].Get()->SetDefault(Location0.X);
|
|
}
|
|
if (DoubleChannels[1].Get())
|
|
{
|
|
DoubleChannels[1].Get()->SetDefault(Location0.Y);
|
|
}
|
|
if (DoubleChannels[2].Get())
|
|
{
|
|
DoubleChannels[2].Get()->SetDefault(Location0.Z);
|
|
}
|
|
if (DoubleChannels[3].Get())
|
|
{
|
|
DoubleChannels[3].Get()->SetDefault(Rotation0.Roll);
|
|
}
|
|
if (DoubleChannels[4].Get())
|
|
{
|
|
DoubleChannels[4].Get()->SetDefault(Rotation0.Pitch);
|
|
}
|
|
if (DoubleChannels[5].Get())
|
|
{
|
|
DoubleChannels[5].Get()->SetDefault(Rotation0.Yaw);
|
|
}
|
|
if (DoubleChannels[6].Get())
|
|
{
|
|
DoubleChannels[6].Get()->SetDefault(Scale3D0.X);
|
|
}
|
|
if (DoubleChannels[7].Get())
|
|
{
|
|
DoubleChannels[7].Get()->SetDefault(Scale3D0.Y);
|
|
}
|
|
if (DoubleChannels[8].Get())
|
|
{
|
|
DoubleChannels[8].Get()->SetDefault(Scale3D0.Z);
|
|
}
|
|
}
|
|
|
|
return TransformSection;
|
|
}
|
|
|
|
bool MovieSceneToolHelpers::AddTransformKeys(
|
|
const UMovieScene3DTransformSection* InTransformSection,
|
|
const TArray<FFrameNumber>& Frames,
|
|
const TArray<FTransform>& InLocalTransforms,
|
|
const EMovieSceneTransformChannel& InChannels)
|
|
{
|
|
if (!InTransformSection)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Frames.IsEmpty() || Frames.Num() != InLocalTransforms.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto GetValue = [](const uint32 Index, const FVector& InLocation, const FRotator& InRotation, const FVector& InScale)
|
|
{
|
|
switch (Index)
|
|
{
|
|
case 0:
|
|
return InLocation.X;
|
|
case 1:
|
|
return InLocation.Y;
|
|
case 2:
|
|
return InLocation.Z;
|
|
case 3:
|
|
return InRotation.Roll;
|
|
case 4:
|
|
return InRotation.Pitch;
|
|
case 5:
|
|
return InRotation.Yaw;
|
|
case 6:
|
|
return InScale.X;
|
|
case 7:
|
|
return InScale.Y;
|
|
case 8:
|
|
return InScale.Z;
|
|
default:
|
|
ensure(false);
|
|
break;
|
|
}
|
|
return 0.0;
|
|
};
|
|
|
|
const bool bKeyTranslation = EnumHasAllFlags(InChannels, EMovieSceneTransformChannel::Translation);
|
|
const bool bKeyRotation = EnumHasAllFlags(InChannels, EMovieSceneTransformChannel::Rotation);
|
|
const bool bKeyScale = EnumHasAllFlags(InChannels, EMovieSceneTransformChannel::Scale);
|
|
|
|
TArray<uint32> ChannelsIndexToKey;
|
|
if (bKeyTranslation)
|
|
{
|
|
ChannelsIndexToKey.Append({ 0,1,2 });
|
|
}
|
|
if (bKeyRotation)
|
|
{
|
|
ChannelsIndexToKey.Append({ 3,4,5 });
|
|
}
|
|
if (bKeyScale)
|
|
{
|
|
ChannelsIndexToKey.Append({ 6,7,8 });
|
|
}
|
|
|
|
FMovieSceneChannelProxy& SectionChannelProxy = InTransformSection->GetChannelProxy();
|
|
TMovieSceneChannelHandle<FMovieSceneDoubleChannel> DoubleChannels[] = {
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.Z"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.Z"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.X"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.Y"),
|
|
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.Z")
|
|
};
|
|
|
|
// set default
|
|
const FTransform& LocalTransform0 = InLocalTransforms[0];
|
|
const FVector Location0 = LocalTransform0.GetLocation();
|
|
const FRotator Rotation0 = LocalTransform0.GetRotation().Rotator();
|
|
const FVector Scale3D0 = LocalTransform0.GetScale3D();
|
|
|
|
for (int32 ChannelIndex = 0; ChannelIndex < 9; ChannelIndex++)
|
|
{
|
|
if (DoubleChannels[ChannelIndex].Get())
|
|
{
|
|
if (!DoubleChannels[ChannelIndex].Get()->GetDefault().IsSet())
|
|
{
|
|
const double Value = GetValue(ChannelIndex, Location0, Rotation0, Scale3D0);
|
|
DoubleChannels[ChannelIndex].Get()->SetDefault(Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add keys
|
|
for (int32 Index = 0; Index < Frames.Num(); ++Index)
|
|
{
|
|
const FFrameNumber& Frame = Frames[Index];
|
|
const FTransform& LocalTransform = InLocalTransforms[Index];
|
|
|
|
const FVector Location = LocalTransform.GetLocation();
|
|
const FRotator Rotation = LocalTransform.GetRotation().Rotator();
|
|
const FVector Scale3D = LocalTransform.GetScale3D();
|
|
|
|
for (const int32 ChannelIndex : ChannelsIndexToKey)
|
|
{
|
|
if (DoubleChannels[ChannelIndex].Get())
|
|
{
|
|
const double Value = GetValue(ChannelIndex, Location, Rotation, Scale3D);
|
|
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = DoubleChannels[ChannelIndex].Get()->GetData();
|
|
MovieSceneToolHelpers::SetOrAddKey(ChannelData, Frame, Value);
|
|
}
|
|
}
|
|
}
|
|
//need to handle euler flips.. 3,4,5 is rotation
|
|
if (bKeyRotation)
|
|
{
|
|
//figure out start/endframe, frames may not be in order
|
|
FFrameNumber StartTime = Frames[0], EndTime = Frames[0];
|
|
for (const FFrameNumber& Frame : Frames)
|
|
{
|
|
if (Frame < StartTime)
|
|
{
|
|
StartTime = Frame;
|
|
}
|
|
else if (Frame > EndTime)
|
|
{
|
|
EndTime = Frame;
|
|
}
|
|
}
|
|
TSortedMap<FFrameNumber, FFrameNumber> FrameSet;
|
|
TRange<FFrameNumber> WithinRange(0, 0);
|
|
WithinRange.SetLowerBoundValue(StartTime);
|
|
WithinRange.SetUpperBoundValue(EndTime);
|
|
TArray<FFrameNumber> KeyTimes;
|
|
TArray<FKeyHandle> KeyHandles;
|
|
for (int32 ChannelIndex = 3; ChannelIndex <=5; ++ChannelIndex)
|
|
{
|
|
KeyTimes.SetNum(0);
|
|
KeyHandles.SetNum(0);
|
|
if (DoubleChannels[ChannelIndex].Get())
|
|
{
|
|
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = DoubleChannels[ChannelIndex].Get()->GetData();
|
|
ChannelData.GetKeys(WithinRange, &KeyTimes, &KeyHandles);
|
|
for (int32 KeyIndex = 0; KeyIndex < KeyTimes.Num() - 1; ++KeyIndex)
|
|
{
|
|
const int32 ValueIndex = ChannelData.GetIndex(KeyHandles[KeyIndex]);
|
|
const int32 NextValueIndex = ChannelData.GetIndex(KeyHandles[KeyIndex + 1]);
|
|
|
|
TArrayView<const FMovieSceneDoubleValue> Values = DoubleChannels[ChannelIndex].Get()->GetValues(); //re-get the array since the winding may change values
|
|
double Value = Values[ValueIndex].Value;
|
|
double NextValue = Values[NextValueIndex].Value;
|
|
FMath::WindRelativeAnglesDegrees(Value,NextValue);
|
|
AssignValue(DoubleChannels[ChannelIndex].Get(), KeyHandles[KeyIndex + 1], NextValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//now we need to set auto tangents
|
|
for (const int32 ChannelIndex : ChannelsIndexToKey)
|
|
{
|
|
if (DoubleChannels[ChannelIndex].Get())
|
|
{
|
|
DoubleChannels[ChannelIndex].Get()->AutoSetTangents();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AddUnwoundKey(FMovieSceneDoubleChannel& Channel, FFrameNumber Time, double Value)
|
|
{
|
|
int32 Index = Channel.AddLinearKey(Time, Value);
|
|
|
|
TArrayView<FMovieSceneDoubleValue> Values = Channel.GetData().GetValues();
|
|
if (Index >= 1)
|
|
{
|
|
const double PreviousValue = Values[Index - 1].Value;
|
|
double NewValue = Value;
|
|
|
|
while (NewValue - PreviousValue > 180.0f)
|
|
{
|
|
NewValue -= 360.f;
|
|
}
|
|
while (NewValue - PreviousValue < -180.0f)
|
|
{
|
|
NewValue += 360.f;
|
|
}
|
|
|
|
Values[Index].Value = NewValue;
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::ImportAnimSequenceTransforms(const TSharedRef<ISequencer>& Sequencer, const FAssetData& Asset, UMovieScene3DTransformTrack* TransformTrack)
|
|
{
|
|
if (!IsValid(TransformTrack))
|
|
{
|
|
return;
|
|
}
|
|
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
|
|
FQualifiedFrameTime CurrentTime = Sequencer->GetLocalTime();
|
|
|
|
UAnimSequence* AnimSequence = Cast<UAnimSequence>(Asset.GetAsset());
|
|
|
|
// find object binding to recover any component transforms we need to incorporate (for characters)
|
|
FTransform InvComponentTransform;
|
|
UMovieSceneSequence* MovieSceneSequence = Sequencer->GetFocusedMovieSceneSequence();
|
|
if(MovieSceneSequence)
|
|
{
|
|
UMovieScene* MovieScene = MovieSceneSequence->GetMovieScene();
|
|
if(MovieScene)
|
|
{
|
|
FGuid ObjectBinding;
|
|
if(MovieScene->FindTrackBinding(*TransformTrack, ObjectBinding))
|
|
{
|
|
const UClass* ObjectClass = nullptr;
|
|
if(FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBinding))
|
|
{
|
|
ObjectClass = Spawnable->GetObjectTemplate()->GetClass();
|
|
}
|
|
else if(FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectBinding))
|
|
{
|
|
ObjectClass = Possessable->GetPossessedObjectClass();
|
|
}
|
|
|
|
if(ObjectClass)
|
|
{
|
|
const ACharacter* Character = Cast<const ACharacter>(ObjectClass->GetDefaultObject(false));
|
|
if(Character)
|
|
{
|
|
const USkeletalMeshComponent* SkeletalMeshComponent = Character->GetMesh();
|
|
FTransform MeshRelativeTransform = SkeletalMeshComponent->GetRelativeTransform();
|
|
InvComponentTransform = MeshRelativeTransform.GetRelativeTransform(SkeletalMeshComponent->GetOwner()->GetTransform()).Inverse();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (AnimSequence && AnimSequence->GetDataModel()->GetNumBoneTracks() > 0)
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT( "Sequencer", "ImportAnimSequenceTransforms", "Import Anim Sequence Transforms" ) );
|
|
|
|
TransformTrack->Modify();
|
|
|
|
UMovieScene3DTransformSection* Section = Cast<UMovieScene3DTransformSection>(TransformTrack->CreateNewSection());
|
|
|
|
Section->SetBlendType(EMovieSceneBlendType::Additive);
|
|
Section->SetMask(EMovieSceneTransformChannel::Translation | EMovieSceneTransformChannel::Rotation);
|
|
|
|
FFrameRate TickResolution = Section->GetTypedOuter<UMovieScene>()->GetTickResolution();
|
|
|
|
// We know that there aren't any channel overrides (all 9 channels are double channels) because we're working
|
|
// on a brand new transform section.
|
|
TArrayView<FMovieSceneDoubleChannel*> DoubleChannels = Section->GetChannelProxy().GetChannels<FMovieSceneDoubleChannel>();
|
|
|
|
// Set default translation and rotation
|
|
for (int32 Index = 0; Index < 6; ++Index)
|
|
{
|
|
DoubleChannels[Index]->SetDefault(0.f);
|
|
}
|
|
// Set default scale
|
|
for (int32 Index = 6; Index < 9; ++Index)
|
|
{
|
|
DoubleChannels[Index]->SetDefault(1.f);
|
|
}
|
|
|
|
TransformTrack->AddSection(*Section);
|
|
|
|
if (Section->TryModify())
|
|
{
|
|
struct FTempTransformKey
|
|
{
|
|
FTransform Transform;
|
|
FRotator WoundRotation;
|
|
float Time;
|
|
};
|
|
|
|
float MinTime = FLT_MAX;
|
|
|
|
TArray<FTempTransformKey> TempKeys;
|
|
|
|
TArray<FName> BoneTrackNames;
|
|
AnimSequence->GetDataModelInterface()->GetBoneTrackNames(BoneTrackNames);
|
|
|
|
TArray<FTransform> BoneTransforms;
|
|
AnimSequence->GetDataModelInterface()->GetBoneTrackTransforms(BoneTrackNames[0], BoneTransforms);
|
|
|
|
for(int32 KeyIndex = 0; KeyIndex < BoneTransforms.Num(); KeyIndex++)
|
|
{
|
|
FTempTransformKey TempKey;
|
|
TempKey.Time = AnimSequence->GetTimeAtFrame(KeyIndex);
|
|
TempKey.Transform = BoneTransforms[KeyIndex];
|
|
|
|
// apply component transform if any
|
|
TempKey.Transform = InvComponentTransform * TempKey.Transform;
|
|
|
|
TempKey.WoundRotation = TempKey.Transform.GetRotation().Rotator();
|
|
|
|
TempKeys.Add(TempKey);
|
|
|
|
MinTime = FMath::Min(MinTime, TempKey.Time);
|
|
}
|
|
|
|
int32 TransformCount = TempKeys.Num();
|
|
for(int32 TransformIndex = 0; TransformIndex < TransformCount - 1; TransformIndex++)
|
|
{
|
|
FRotator& Rotator = TempKeys[TransformIndex].WoundRotation;
|
|
FRotator& NextRotator = TempKeys[TransformIndex + 1].WoundRotation;
|
|
|
|
FMath::WindRelativeAnglesDegrees(Rotator.Pitch, NextRotator.Pitch);
|
|
FMath::WindRelativeAnglesDegrees(Rotator.Yaw, NextRotator.Yaw);
|
|
FMath::WindRelativeAnglesDegrees(Rotator.Roll, NextRotator.Roll);
|
|
}
|
|
|
|
FFrameNumber MinKeyTime = (MinTime * TickResolution).RoundToFrame();
|
|
|
|
TRange<FFrameNumber> Range = Section->GetRange();
|
|
for(const FTempTransformKey& TempKey : TempKeys)
|
|
{
|
|
FFrameNumber KeyTime = (TempKey.Time * TickResolution).RoundToFrame();
|
|
|
|
KeyTime = CurrentTime.Time.FrameNumber + (KeyTime - MinKeyTime);
|
|
|
|
Range = TRange<FFrameNumber>::Hull(Range, TRange<FFrameNumber>(KeyTime));
|
|
|
|
const FVector3f Translation = (FVector3f)TempKey.Transform.GetTranslation();
|
|
const FVector3f Rotation = (FVector3f)TempKey.WoundRotation.Euler();
|
|
const FVector3f Scale = (FVector3f)TempKey.Transform.GetScale3D();
|
|
|
|
TArrayView<FMovieSceneDoubleChannel*> Channels = Section->GetChannelProxy().GetChannels<FMovieSceneDoubleChannel>();
|
|
|
|
Channels[0]->AddLinearKey(KeyTime, Translation.X);
|
|
Channels[1]->AddLinearKey(KeyTime, Translation.Y);
|
|
Channels[2]->AddLinearKey(KeyTime, Translation.Z);
|
|
|
|
AddUnwoundKey(*Channels[3], KeyTime, Rotation.X);
|
|
AddUnwoundKey(*Channels[4], KeyTime, Rotation.Y);
|
|
AddUnwoundKey(*Channels[5], KeyTime, Rotation.Z);
|
|
|
|
Channels[6]->AddLinearKey(KeyTime, Scale.X);
|
|
Channels[7]->AddLinearKey(KeyTime, Scale.Y);
|
|
Channels[8]->AddLinearKey(KeyTime, Scale.Z);
|
|
}
|
|
|
|
Section->SetRange(Range);
|
|
Section->SetRowIndex(FindAvailableRowIndex(TransformTrack, Section));
|
|
|
|
Sequencer->NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemAdded );
|
|
}
|
|
}
|
|
}
|
|
|
|
void MovieSceneToolHelpers::ImportAnimSequenceTransformsEnterPressed(const TSharedRef<ISequencer>& Sequencer, const TArray<FAssetData>& Asset, UMovieScene3DTransformTrack* TransformTrack)
|
|
{
|
|
if (Asset.Num() > 0)
|
|
{
|
|
ImportAnimSequenceTransforms(Sequencer, Asset[0].GetAsset(), TransformTrack);
|
|
}
|
|
}
|