1137 lines
40 KiB
C++
1137 lines
40 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PoseSearchDatabaseViewModel.h"
|
|
#include "AnimPreviewInstance.h"
|
|
#include "Animation/AnimComposite.h"
|
|
#include "Animation/AnimMontage.h"
|
|
#include "Animation/AnimSequence.h"
|
|
#include "Animation/BlendSpace.h"
|
|
#include "Animation/DebugSkelMeshComponent.h"
|
|
#include "Animation/TrajectoryTypes.h"
|
|
#include "Animation/MirrorDataTable.h"
|
|
#include "Camera/CameraComponent.h"
|
|
#include "Components/CapsuleComponent.h"
|
|
#include "EngineUtils.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "StructUtils/InstancedStruct.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "PoseSearch/MultiAnimAsset.h"
|
|
#include "PoseSearch/PoseSearchAnimNotifies.h"
|
|
#include "PoseSearch/PoseSearchContext.h"
|
|
#include "PoseSearch/PoseSearchDatabase.h"
|
|
#include "PoseSearch/PoseSearchDefines.h"
|
|
#include "PoseSearch/PoseSearchDerivedData.h"
|
|
#include "PoseSearch/PoseSearchHistory.h"
|
|
#include "PoseSearch/PoseSearchSchema.h"
|
|
#include "PoseSearchDatabaseAssetTreeNode.h"
|
|
#include "PoseSearchDatabaseDataDetails.h"
|
|
#include "PoseSearchDatabasePreviewScene.h"
|
|
#include "PoseSearchEditor.h"
|
|
#include "PropertyEditorModule.h"
|
|
|
|
namespace UE::PoseSearch
|
|
{
|
|
#if ENABLE_ANIM_DEBUG
|
|
static float GVarDatabasePreviewDebugDrawSamplerSize = 0.f;
|
|
static FAutoConsoleVariableRef CVarDatabasePreviewDebugDrawSamplerSize(TEXT("a.DatabasePreview.DebugDrawSamplerSize"), GVarDatabasePreviewDebugDrawSamplerSize, TEXT("Debug Draw Sampler Positions Size"));
|
|
|
|
static float GVarDatabasePreviewDebugDrawSamplerTimeOffset = 0.f;
|
|
static FAutoConsoleVariableRef CVarDatabasePreviewDebugDrawSamplerTimeOffset(TEXT("a.DatabasePreview.DebugDrawSamplerTimeOffset"), GVarDatabasePreviewDebugDrawSamplerTimeOffset, TEXT("Debug Draw Sampler Positions At Time Offset"));
|
|
|
|
static float GVarDatabasePreviewDebugDrawSamplerRootAxisLength = 0.f;
|
|
static FAutoConsoleVariableRef CVarDatabasePreviewDebugDrawSamplerRootAxisLength(TEXT("a.DatabasePreview.DebugDrawSamplerRootAxisLength"), GVarDatabasePreviewDebugDrawSamplerRootAxisLength, TEXT("Debug Draw Sampler Root Axis Length"));
|
|
#endif
|
|
|
|
// FDatabasePreviewActor
|
|
bool FDatabasePreviewActor::SpawnPreviewActor(UWorld* World, const UPoseSearchDatabase* PoseSearchDatabase, int32 IndexAssetIdx, const FRole& Role, const FTransform& SamplerRootTransformOrigin, int32 PoseIdxForTimeOffset)
|
|
{
|
|
check(PoseSearchDatabase && PoseSearchDatabase->Schema);
|
|
const FSearchIndex& SearchIndex = PoseSearchDatabase->GetSearchIndex();
|
|
const FSearchIndexAsset& IndexAsset = SearchIndex.Assets[IndexAssetIdx];
|
|
const FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = PoseSearchDatabase->GetDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(IndexAsset.GetSourceAssetIdx());
|
|
check(DatabaseAnimationAsset);
|
|
|
|
USkeleton* Skeleton = PoseSearchDatabase->Schema->GetSkeleton(Role);
|
|
if (!Skeleton)
|
|
{
|
|
UE_LOG(LogPoseSearchEditor, Log, TEXT("Couldn't spawn preview Actor for asset %s because its Role '%s' is missing in Schema '%s'"), *GetNameSafe(DatabaseAnimationAsset->GetAnimationAsset()), *Role.ToString(), *PoseSearchDatabase->Schema->GetName());
|
|
return false;
|
|
}
|
|
|
|
UAnimationAsset* PreviewAsset = Cast<UAnimationAsset>(DatabaseAnimationAsset->GetAnimationAssetForRole(Role));
|
|
if (!PreviewAsset)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ActorRole = Role;
|
|
IndexAssetIndex = IndexAssetIdx;
|
|
CurrentPoseIndex = INDEX_NONE;
|
|
|
|
FActorSpawnParameters Params;
|
|
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
|
|
ActorPtr = World->SpawnActor<AActor>(AActor::StaticClass(), FTransform::Identity, Params);
|
|
ActorPtr->SetFlags(RF_Transient);
|
|
|
|
UDebugSkelMeshComponent* Mesh = NewObject<UDebugSkelMeshComponent>(ActorPtr.Get());
|
|
Mesh->RegisterComponentWithWorld(World);
|
|
|
|
UAnimPreviewInstance* AnimInstance = NewObject<UAnimPreviewInstance>(Mesh);
|
|
Mesh->PreviewInstance = AnimInstance;
|
|
AnimInstance->InitializeAnimation();
|
|
|
|
USkeletalMesh* PreviewMesh = DatabaseAnimationAsset->GetPreviewMeshForRole(Role);
|
|
if (!PreviewMesh)
|
|
{
|
|
PreviewMesh = PoseSearchDatabase->PreviewMesh;
|
|
if (!PreviewMesh)
|
|
{
|
|
PreviewMesh = Skeleton->GetPreviewMesh(true);
|
|
}
|
|
}
|
|
|
|
Mesh->SetSkeletalMesh(PreviewMesh);
|
|
Mesh->EnablePreview(true, PreviewAsset);
|
|
|
|
AnimInstance->SetAnimationAsset(PreviewAsset, IndexAsset.IsLooping(), 0.f);
|
|
AnimInstance->SetBlendSpacePosition(IndexAsset.GetBlendParameters());
|
|
|
|
if (IndexAsset.IsMirrored())
|
|
{
|
|
const UMirrorDataTable* MirrorDataTable = PoseSearchDatabase->Schema->GetMirrorDataTable(Role);
|
|
AnimInstance->SetMirrorDataTable(MirrorDataTable);
|
|
}
|
|
|
|
const FMirrorDataCache MirrorDataCache(AnimInstance->GetMirrorDataTable(), AnimInstance->GetRequiredBonesOnAnyThread());
|
|
|
|
Sampler.Init(PreviewAsset, SamplerRootTransformOrigin, IndexAsset.GetBlendParameters());
|
|
|
|
PlayTimeOffset = 0.f;
|
|
if (PoseIdxForTimeOffset >= 0)
|
|
{
|
|
PlayTimeOffset = PoseSearchDatabase->GetRealAssetTime(PoseIdxForTimeOffset) - IndexAsset.GetFirstSampleTime(PoseSearchDatabase->Schema->SampleRate);
|
|
|
|
if (DatabaseAnimationAsset->GetNumRoles() > 1)
|
|
{
|
|
// @todo: implement support for UMultiAnimAsset. the transform should be centered to the origin of the multi character animation!
|
|
}
|
|
else
|
|
{
|
|
// centering the Sampler RootTransformOrigin at PlayTimeOffset time, to be able to "align" multiple actors from different animation frames when selected by the pose search debugger
|
|
FTransform NewSamplerRootTransformOrigin = MirrorDataCache.MirrorTransform(Sampler.ExtractRootTransform(0.f));
|
|
NewSamplerRootTransformOrigin.SetToRelativeTransform(MirrorDataCache.MirrorTransform(Sampler.ExtractRootTransform(PlayTimeOffset)));
|
|
Sampler.SetRootTransformOrigin(NewSamplerRootTransformOrigin);
|
|
}
|
|
}
|
|
|
|
AnimInstance->PlayAnim(IndexAsset.IsLooping(), 0.f);
|
|
if (!ActorPtr->GetRootComponent())
|
|
{
|
|
ActorPtr->SetRootComponent(Mesh);
|
|
}
|
|
|
|
AnimInstance->SetPlayRate(0.f);
|
|
|
|
// initializing Trajectory and TrajectorySpeed
|
|
const int NumPoses = IndexAsset.GetNumPoses();
|
|
Trajectory.Samples.SetNumUninitialized(NumPoses);
|
|
TrajectorySpeed.SetNumUninitialized(NumPoses);
|
|
|
|
for (int32 Index = 0; Index < NumPoses; ++Index)
|
|
{
|
|
const int32 IndexAssetPoseIdx = Index + IndexAsset.GetFirstPoseIdx();
|
|
const float IndexAssetPoseTime = IndexAsset.GetTimeFromPoseIndex(IndexAssetPoseIdx, PoseSearchDatabase->Schema->SampleRate);
|
|
const FTransform IndexAssetPoseTransform = MirrorDataCache.MirrorTransform(Sampler.ExtractRootTransform(IndexAssetPoseTime));
|
|
|
|
Trajectory.Samples[Index].SetTransform(IndexAssetPoseTransform);
|
|
Trajectory.Samples[Index].TimeInSeconds = IndexAssetPoseTime;
|
|
}
|
|
|
|
for (int32 Index = 1; Index < NumPoses; ++Index)
|
|
{
|
|
const float DeltaAccumulatedSeconds = FMath::Max(UE_KINDA_SMALL_NUMBER, Trajectory.Samples[Index].TimeInSeconds - Trajectory.Samples[Index - 1].TimeInSeconds);
|
|
const FVector& Start = Trajectory.Samples[Index - 1].Position;
|
|
const FVector& End = Trajectory.Samples[Index].Position;
|
|
TrajectorySpeed[Index] = (Start - End).Length() / DeltaAccumulatedSeconds;
|
|
}
|
|
|
|
if (NumPoses > 0)
|
|
{
|
|
TrajectorySpeed[0] = NumPoses > 1 ? TrajectorySpeed[1] : 0.f;
|
|
}
|
|
|
|
UE_LOG(LogPoseSearchEditor, Log, TEXT("Spawned preview Actor: %s"), *GetNameSafe(ActorPtr.Get()));
|
|
return true;
|
|
}
|
|
|
|
void FDatabasePreviewActor::UpdatePreviewActor(const UPoseSearchDatabase* PoseSearchDatabase, float PlayTime, bool bQuantizeAnimationToPoseData)
|
|
{
|
|
check(PoseSearchDatabase);
|
|
|
|
const FSearchIndex& SearchIndex = PoseSearchDatabase->GetSearchIndex();
|
|
|
|
UAnimPreviewInstance* AnimInstance = GetAnimPreviewInstanceInternal();
|
|
if (!AnimInstance || !SearchIndex.Assets.IsValidIndex(IndexAssetIndex))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const UAnimationAsset* PreviewAsset = AnimInstance->GetAnimationAsset();
|
|
if (!PreviewAsset)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UDebugSkelMeshComponent* Mesh = GetDebugSkelMeshComponent();
|
|
if (!Mesh)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FSearchIndexAsset& IndexAsset = SearchIndex.Assets[IndexAssetIndex];
|
|
const FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = PoseSearchDatabase->GetDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(IndexAsset.GetSourceAssetIdx());
|
|
|
|
USkeletalMesh* PreviewMesh = DatabaseAnimationAsset->GetPreviewMeshForRole(ActorRole);
|
|
if (!PreviewMesh)
|
|
{
|
|
PreviewMesh = PoseSearchDatabase->PreviewMesh;
|
|
if (!PreviewMesh)
|
|
{
|
|
if (USkeleton* Skeleton = PoseSearchDatabase->Schema->GetSkeleton(ActorRole))
|
|
{
|
|
PreviewMesh = Skeleton->GetPreviewMesh(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Mesh->GetSkeletalMeshAsset() != PreviewMesh)
|
|
{
|
|
Mesh->SetSkeletalMesh(PreviewMesh);
|
|
}
|
|
|
|
CurrentTime = 0.f;
|
|
float CurrentPlayTime = PlayTime + IndexAsset.GetFirstSampleTime(PoseSearchDatabase->Schema->SampleRate) + PlayTimeOffset;
|
|
FAnimationRuntime::AdvanceTime(false, CurrentPlayTime, CurrentTime, IndexAsset.GetLastSampleTime(PoseSearchDatabase->Schema->SampleRate));
|
|
|
|
// time to pose index
|
|
CurrentPoseIndex = IndexAsset.GetPoseIndexFromTime(CurrentTime, PoseSearchDatabase->Schema->SampleRate);
|
|
|
|
QuantizedTime = CurrentPoseIndex >= 0 ? PoseSearchDatabase->GetRealAssetTime(CurrentPoseIndex) : CurrentTime;
|
|
if (bQuantizeAnimationToPoseData)
|
|
{
|
|
CurrentTime = QuantizedTime;
|
|
}
|
|
|
|
// SetPosition is in [0..1] range for blendspaces
|
|
AnimInstance->SetPosition(Sampler.ToNormalizedTime(CurrentTime));
|
|
AnimInstance->SetPlayRate(0.f);
|
|
AnimInstance->SetBlendSpacePosition(IndexAsset.GetBlendParameters());
|
|
|
|
check(ActorPtr != nullptr);
|
|
ActorPtr->SetActorTransform(Trajectory.GetSampleAtTime(CurrentTime).GetTransform());
|
|
}
|
|
|
|
void FDatabasePreviewActor::Destroy()
|
|
{
|
|
if (ActorPtr != nullptr)
|
|
{
|
|
ActorPtr->Destroy();
|
|
}
|
|
}
|
|
|
|
bool FDatabasePreviewActor::DrawPreviewActors(TConstArrayView<FDatabasePreviewActor> PreviewActors, const UPoseSearchDatabase* PoseSearchDatabase, bool bDisplayRootMotionSpeed, bool bDisplayBlockTransition, bool bDisplayEventData, TConstArrayView<float> QueryVector)
|
|
{
|
|
using namespace UE::PoseSearch;
|
|
|
|
UWorld* CommonWorld = nullptr;
|
|
int32 CommonCurrentPoseIndex = INDEX_NONE;
|
|
#if DO_CHECK
|
|
int32 CommonIndexAssetIndex = INDEX_NONE;
|
|
#endif // DO_CHECK
|
|
|
|
|
|
TArray<FChooserEvaluationContext, TInlineAllocator<PreallocatedRolesNum>> AnimContextsData;
|
|
TArray<FChooserEvaluationContext*, TInlineAllocator<PreallocatedRolesNum>> AnimContexts;
|
|
FRoleToIndex RoleToIndex;
|
|
TArray<FArchivedPoseHistory, TInlineAllocator<PreallocatedRolesNum>> ArchivedPoseHistories;
|
|
TArray<const IPoseHistory*, TInlineAllocator<PreallocatedRolesNum>> PoseHistories;
|
|
|
|
const int32 NumPreviewActors = PreviewActors.Num();
|
|
if (NumPreviewActors > PreallocatedRolesNum)
|
|
{
|
|
// reserve the needed amount of memory for containers
|
|
AnimContexts.Reserve(NumPreviewActors);
|
|
RoleToIndex.Reserve(NumPreviewActors);
|
|
ArchivedPoseHistories.Reserve(NumPreviewActors);
|
|
PoseHistories.Reserve(NumPreviewActors);
|
|
}
|
|
|
|
AnimContextsData.SetNum(NumPreviewActors);
|
|
|
|
for (const FDatabasePreviewActor& PreviewActor : PreviewActors)
|
|
{
|
|
const FSearchIndex& SearchIndex = PoseSearchDatabase->GetSearchIndex();
|
|
|
|
// This condition happens when the database got reindexed and the new valid SearchIndex has different cardinality for assets or poses.
|
|
// Since we didn't refresh the PreviewActor IndexAssetIndex nor CurrentPoseIndex, now the PreviewActor is invalid
|
|
// @todo: we should refresh the PreviewActor and restore it's preview time etc
|
|
if (!SearchIndex.Assets.IsValidIndex(PreviewActor.GetIndexAssetIndex()) ||
|
|
!SearchIndex.IsValidPoseIndex(PreviewActor.GetCurrentPoseIndex()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const UDebugSkelMeshComponent* Mesh = PreviewActor.GetDebugSkelMeshComponent();
|
|
if (!Mesh)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!CommonWorld)
|
|
{
|
|
CommonWorld = Mesh->GetWorld();
|
|
}
|
|
else if (CommonWorld != Mesh->GetWorld())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// making sure PreviewActors are consistent with each other
|
|
if (CommonCurrentPoseIndex == INDEX_NONE)
|
|
{
|
|
CommonCurrentPoseIndex = PreviewActor.GetCurrentPoseIndex();
|
|
}
|
|
else if (CommonCurrentPoseIndex != PreviewActor.GetCurrentPoseIndex())
|
|
{
|
|
checkNoEntry();
|
|
return false;
|
|
}
|
|
|
|
#if DO_CHECK
|
|
if (CommonIndexAssetIndex == INDEX_NONE)
|
|
{
|
|
CommonIndexAssetIndex = PreviewActor.GetIndexAssetIndex();
|
|
}
|
|
else if (CommonIndexAssetIndex != PreviewActor.GetIndexAssetIndex())
|
|
{
|
|
checkNoEntry();
|
|
return false;
|
|
}
|
|
#endif // DO_CHECK
|
|
|
|
int Index = AnimContexts.Num();
|
|
RoleToIndex.Add(PreviewActor.ActorRole) = AnimContexts.Num();
|
|
AnimContexts.Add(&AnimContextsData[Index]);
|
|
AnimContextsData[Index].AddObjectParam(const_cast<UDebugSkelMeshComponent*>(Mesh));
|
|
|
|
FArchivedPoseHistory& ArchivedPoseHistory = ArchivedPoseHistories.AddDefaulted_GetRef();
|
|
ArchivedPoseHistory.Trajectory = PreviewActor.Trajectory;
|
|
|
|
check(PoseSearchDatabase && PoseSearchDatabase->Schema);
|
|
if (const USkeleton* Skeleton = PoseSearchDatabase->Schema->GetSkeleton(PreviewActor.ActorRole))
|
|
{
|
|
// reconstructing ArchivedPoseHistory::BoneToTransformMap and ArchivedPoseHistory::Entries ONLY for the root bone.
|
|
// @todo: add more bones if needed
|
|
const TArray<FTransform>& RefBonePose = Skeleton->GetReferenceSkeleton().GetRefBonePose();
|
|
const FTransform& RefRootBone = RefBonePose[RootBoneIndexType];
|
|
|
|
ArchivedPoseHistory.BoneToTransformMap.Add(RootBoneIndexType) = RootBoneIndexType;
|
|
FPoseHistoryEntry& PoseHistoryEntry = ArchivedPoseHistory.Entries.AddDefaulted_GetRef();
|
|
// saving space for the root bone only
|
|
PoseHistoryEntry.SetNum(1, true);
|
|
PoseHistoryEntry.SetComponentSpaceTransform(RootBoneIndexType, RefRootBone);
|
|
}
|
|
|
|
for (FTransformTrajectorySample& TrajectorySample : ArchivedPoseHistory.Trajectory.Samples)
|
|
{
|
|
TrajectorySample.TimeInSeconds -= PreviewActor.QuantizedTime;
|
|
}
|
|
|
|
PoseHistories.Add(&ArchivedPoseHistory);
|
|
}
|
|
|
|
UE::PoseSearch::FDebugDrawParams DrawParams(AnimContexts, PoseHistories, RoleToIndex, PoseSearchDatabase);
|
|
DrawParams.DrawFeatureVector(CommonCurrentPoseIndex);
|
|
|
|
if (!QueryVector.IsEmpty())
|
|
{
|
|
DrawParams.DrawFeatureVector(QueryVector);
|
|
}
|
|
|
|
for (const FDatabasePreviewActor& PreviewActor : PreviewActors)
|
|
{
|
|
const UDebugSkelMeshComponent* Mesh = PreviewActor.GetDebugSkelMeshComponent();
|
|
const FSearchIndex& SearchIndex = PoseSearchDatabase->GetSearchIndex();
|
|
const FSearchIndexAsset& IndexAsset = SearchIndex.Assets[PreviewActor.GetIndexAssetIndex()];
|
|
const int32 SamplesNum = PreviewActor.Trajectory.Samples.Num();
|
|
|
|
if (bDisplayRootMotionSpeed)
|
|
{
|
|
// @todo: should we be using PreviewActor.Trajectory.DebugDrawTrajectory instead?
|
|
// drawing PreviewActor.Trajectory
|
|
|
|
if (SamplesNum > 1)
|
|
{
|
|
for (int32 Index = 0; Index < SamplesNum; ++Index)
|
|
{
|
|
const FVector& EndDown = PreviewActor.Trajectory.Samples[Index].Position;
|
|
const FVector EndUp = EndDown + (PreviewActor.TrajectorySpeed[Index] * FVector::UpVector);
|
|
|
|
DrawParams.DrawLine(EndDown, EndUp, FColor::Black);
|
|
if (Index > 0)
|
|
{
|
|
const FColor RootMotionColor = Index % 2 == 0 ? FColor::Purple : FColor::Orange;
|
|
const FVector& StartDown = PreviewActor.Trajectory.Samples[Index - 1].Position;
|
|
const FVector StartUp = StartDown + (PreviewActor.TrajectorySpeed[Index - 1] * FVector::UpVector);
|
|
DrawParams.DrawLine(StartDown, EndDown, RootMotionColor);
|
|
DrawParams.DrawLine(StartUp, EndUp, RootMotionColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bDisplayBlockTransition)
|
|
{
|
|
const int NumPoses = IndexAsset.GetNumPoses();
|
|
if (NumPoses == SamplesNum)
|
|
{
|
|
for (int32 Index = 0; Index < SamplesNum; ++Index)
|
|
{
|
|
const int32 IndexAssetPoseIdx = Index + IndexAsset.GetFirstPoseIdx();
|
|
if (SearchIndex.PoseMetadata[IndexAssetPoseIdx].IsBlockTransition())
|
|
{
|
|
DrawParams.DrawPoint(PreviewActor.Trajectory.Samples[Index].Position, FColor::Red);
|
|
}
|
|
else
|
|
{
|
|
DrawParams.DrawPoint(PreviewActor.Trajectory.Samples[Index].Position, FColor::Green);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bDisplayEventData && !SearchIndex.EventData.GetData().IsEmpty())
|
|
{
|
|
const int NumPoses = IndexAsset.GetNumPoses();
|
|
if (NumPoses == SamplesNum)
|
|
{
|
|
TSet<int32, DefaultKeyFuncs<int32>, TInlineSetAllocator<256>> AllEventEventDataPoseIndexes;
|
|
for (const FEventData::FTagToPoseIndexes& TagToPoseIndexes : SearchIndex.EventData.GetData())
|
|
{
|
|
for (int32 PoseIdx : TagToPoseIndexes.Value)
|
|
{
|
|
AllEventEventDataPoseIndexes.Add(PoseIdx);
|
|
}
|
|
}
|
|
|
|
for (int32 Index = 0; Index < SamplesNum; ++Index)
|
|
{
|
|
const int32 IndexAssetPoseIdx = Index + IndexAsset.GetFirstPoseIdx();
|
|
if (AllEventEventDataPoseIndexes.Find(IndexAssetPoseIdx))
|
|
{
|
|
DrawParams.DrawPoint(PreviewActor.Trajectory.Samples[Index].Position, FColor::Blue, 8.f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if ENABLE_ANIM_DEBUG
|
|
const float DebugDrawSamplerSize = GVarDatabasePreviewDebugDrawSamplerSize;
|
|
if (DebugDrawSamplerSize > UE_KINDA_SMALL_NUMBER)
|
|
{
|
|
const float DebugDrawSamplerTimeOffset = GVarDatabasePreviewDebugDrawSamplerTimeOffset;
|
|
|
|
const int32 NumDrawPasses = FMath::IsNearlyZero(DebugDrawSamplerTimeOffset) ? 1 : 2;
|
|
|
|
FMemMark Mark(FMemStack::Get());
|
|
FCompactPose Pose;
|
|
FCSPose<FCompactPose> ComponentSpacePose;
|
|
|
|
const FMirrorDataCache MirrorDataCache(Mesh->PreviewInstance->GetMirrorDataTable(), Mesh->PreviewInstance->GetRequiredBonesOnAnyThread());
|
|
|
|
for (int32 DrawPass = 0; DrawPass < NumDrawPasses; ++DrawPass)
|
|
{
|
|
// drawing the pose extracted from the Sampler to visually compare with the pose features and the mesh drawing
|
|
Pose.SetBoneContainer(&PreviewActor.GetAnimPreviewInstance()->GetRequiredBonesOnAnyThread());
|
|
|
|
const float SamplerTime = DrawPass ? PreviewActor.CurrentTime + DebugDrawSamplerTimeOffset : PreviewActor.CurrentTime;
|
|
const FColor DebugColor = DrawPass ? FColor::Blue : FColor::Red;
|
|
|
|
PreviewActor.Sampler.ExtractPose(SamplerTime, Pose);
|
|
MirrorDataCache.MirrorPose(Pose);
|
|
ComponentSpacePose.InitPose(MoveTemp(Pose));
|
|
|
|
const FTransform RootTransform = MirrorDataCache.MirrorTransform(PreviewActor.Sampler.ExtractRootTransform(SamplerTime));
|
|
const float DebugDrawSamplerRootAxisLength = GVarDatabasePreviewDebugDrawSamplerRootAxisLength;
|
|
if (DebugDrawSamplerRootAxisLength > 0.f)
|
|
{
|
|
DrawParams.DrawLine(RootTransform.GetTranslation(), RootTransform.GetTranslation() + RootTransform.GetScaledAxis(EAxis::X) * DebugDrawSamplerRootAxisLength, FColor::Red);
|
|
DrawParams.DrawLine(RootTransform.GetTranslation(), RootTransform.GetTranslation() + RootTransform.GetScaledAxis(EAxis::Y) * DebugDrawSamplerRootAxisLength, FColor::Green);
|
|
DrawParams.DrawLine(RootTransform.GetTranslation(), RootTransform.GetTranslation() + RootTransform.GetScaledAxis(EAxis::Z) * DebugDrawSamplerRootAxisLength, FColor::Blue);
|
|
}
|
|
|
|
for (int32 BoneIndex = 0; BoneIndex < ComponentSpacePose.GetPose().GetNumBones(); ++BoneIndex)
|
|
{
|
|
const FTransform BoneWorldTransforms = ComponentSpacePose.GetComponentSpaceTransform(FCompactPoseBoneIndex(BoneIndex)) * RootTransform;
|
|
DrawParams.DrawPoint(BoneWorldTransforms.GetTranslation(), DebugColor, DebugDrawSamplerSize);
|
|
}
|
|
}
|
|
}
|
|
#endif // ENABLE_ANIM_DEBUG
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const UDebugSkelMeshComponent* FDatabasePreviewActor::GetDebugSkelMeshComponent() const
|
|
{
|
|
if (ActorPtr != nullptr)
|
|
{
|
|
return Cast<UDebugSkelMeshComponent>(ActorPtr->GetRootComponent());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UDebugSkelMeshComponent* FDatabasePreviewActor::GetDebugSkelMeshComponent()
|
|
{
|
|
if (ActorPtr != nullptr)
|
|
{
|
|
return Cast<UDebugSkelMeshComponent>(ActorPtr->GetRootComponent());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const UAnimPreviewInstance* FDatabasePreviewActor::GetAnimPreviewInstance() const
|
|
{
|
|
if (const UDebugSkelMeshComponent* Mesh = GetDebugSkelMeshComponent())
|
|
{
|
|
return Mesh->PreviewInstance.Get();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UAnimPreviewInstance* FDatabasePreviewActor::GetAnimPreviewInstanceInternal()
|
|
{
|
|
if (ActorPtr != nullptr)
|
|
{
|
|
if (UDebugSkelMeshComponent* Mesh = Cast<UDebugSkelMeshComponent>(ActorPtr->GetRootComponent()))
|
|
{
|
|
return Mesh->PreviewInstance.Get();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// FDatabaseViewModel
|
|
void FDatabaseViewModel::AddReferencedObjects(FReferenceCollector& Collector)
|
|
{
|
|
Collector.AddReferencedObject(PoseSearchDatabasePtr);
|
|
}
|
|
|
|
void FDatabaseViewModel::Initialize(UPoseSearchDatabase* InPoseSearchDatabase, const TSharedRef<FDatabasePreviewScene>& InPreviewScene, const TSharedRef<SDatabaseDataDetails>& InDatabaseDataDetails)
|
|
{
|
|
PoseSearchDatabasePtr = InPoseSearchDatabase;
|
|
PreviewScenePtr = InPreviewScene;
|
|
DatabaseDataDetails = InDatabaseDataDetails;
|
|
|
|
RemovePreviewActors();
|
|
}
|
|
|
|
void FDatabaseViewModel::BuildSearchIndex()
|
|
{
|
|
using namespace UE::PoseSearch;
|
|
FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(GetPoseSearchDatabase(), ERequestAsyncBuildFlag::NewRequest);
|
|
}
|
|
|
|
void FDatabaseViewModel::PreviewBackwardEnd()
|
|
{
|
|
SetPlayTime(MinPreviewPlayLength, false);
|
|
}
|
|
|
|
void FDatabaseViewModel::PreviewBackwardStep()
|
|
{
|
|
const float NewPlayTime = FMath::Clamp(PlayTime - StepDeltaTime, MinPreviewPlayLength, MaxPreviewPlayLength);
|
|
SetPlayTime(NewPlayTime, false);
|
|
}
|
|
|
|
void FDatabaseViewModel::PreviewBackward()
|
|
{
|
|
DeltaTimeMultiplier = -1.f;
|
|
}
|
|
|
|
void FDatabaseViewModel::PreviewPause()
|
|
{
|
|
DeltaTimeMultiplier = 0.f;
|
|
}
|
|
|
|
void FDatabaseViewModel::PreviewForward()
|
|
{
|
|
DeltaTimeMultiplier = 1.f;
|
|
}
|
|
|
|
void FDatabaseViewModel::PreviewForwardStep()
|
|
{
|
|
const float NewPlayTime = FMath::Clamp(PlayTime + StepDeltaTime, MinPreviewPlayLength, MaxPreviewPlayLength);
|
|
SetPlayTime(NewPlayTime, false);
|
|
}
|
|
|
|
void FDatabaseViewModel::PreviewForwardEnd()
|
|
{
|
|
SetPlayTime(MaxPreviewPlayLength, false);
|
|
}
|
|
|
|
UWorld* FDatabaseViewModel::GetWorld()
|
|
{
|
|
check(PreviewScenePtr.IsValid());
|
|
return PreviewScenePtr.Pin()->GetWorld();
|
|
}
|
|
|
|
void FDatabaseViewModel::OnPreviewActorClassChanged()
|
|
{
|
|
// todo: implement
|
|
}
|
|
|
|
void FDatabaseViewModel::Tick(float DeltaSeconds)
|
|
{
|
|
if (!PreviewActors.IsEmpty())
|
|
{
|
|
const float DeltaPlayTime = DeltaSeconds * DeltaTimeMultiplier;
|
|
|
|
const UPoseSearchDatabase* Database = GetPoseSearchDatabase();
|
|
if (EAsyncBuildIndexResult::Success == FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(Database, ERequestAsyncBuildFlag::ContinueRequest))
|
|
{
|
|
PlayTime += DeltaPlayTime;
|
|
PlayTime = FMath::Clamp(PlayTime, MinPreviewPlayLength, MaxPreviewPlayLength);
|
|
|
|
for (TArray<FDatabasePreviewActor>& PreviewActorGroup : PreviewActors)
|
|
{
|
|
for (FDatabasePreviewActor& PreviewActor : PreviewActorGroup)
|
|
{
|
|
PreviewActor.UpdatePreviewActor(Database, PlayTime, bQuantizeAnimationToPoseData);
|
|
}
|
|
}
|
|
|
|
bool bShouldDrawQueryVector = ShouldDrawQueryVector();
|
|
for (TArray<FDatabasePreviewActor>& PreviewActorGroup : PreviewActors)
|
|
{
|
|
bShouldDrawQueryVector &= !FDatabasePreviewActor::DrawPreviewActors(PreviewActorGroup, Database, bDisplayRootMotionSpeed, bDisplayBlockTransition, bDisplayEventData, bShouldDrawQueryVector ? GetQueryVector() : TConstArrayView<float>());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDatabaseViewModel::RemovePreviewActors()
|
|
{
|
|
PlayTime = 0.f;
|
|
DeltaTimeMultiplier = 1.f;
|
|
MaxPreviewPlayLength = 0.f;
|
|
MinPreviewPlayLength = 0.f;
|
|
bIsEditorSelection = true;
|
|
bDrawQueryVector = false;
|
|
|
|
for (TArray<FDatabasePreviewActor>& PreviewActorGroup : PreviewActors)
|
|
{
|
|
for (FDatabasePreviewActor& PreviewActor : PreviewActorGroup)
|
|
{
|
|
PreviewActor.Destroy();
|
|
}
|
|
}
|
|
|
|
PreviewActors.Reset();
|
|
}
|
|
|
|
void FDatabaseViewModel::AddSequenceToDatabase(UAnimSequence* AnimSequence)
|
|
{
|
|
if (UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
Database->Modify();
|
|
|
|
FPoseSearchDatabaseSequence NewAsset;
|
|
NewAsset.Sequence = AnimSequence;
|
|
Database->AddAnimationAsset(FInstancedStruct::Make(NewAsset));
|
|
}
|
|
}
|
|
|
|
void FDatabaseViewModel::AddBlendSpaceToDatabase(UBlendSpace* BlendSpace)
|
|
{
|
|
if (UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
Database->Modify();
|
|
|
|
FPoseSearchDatabaseBlendSpace NewAsset;
|
|
NewAsset.BlendSpace = BlendSpace;
|
|
Database->AddAnimationAsset(FInstancedStruct::Make(NewAsset));
|
|
}
|
|
}
|
|
|
|
void FDatabaseViewModel::AddAnimCompositeToDatabase(UAnimComposite* AnimComposite)
|
|
{
|
|
if (UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
Database->Modify();
|
|
|
|
FPoseSearchDatabaseAnimComposite NewAsset;
|
|
NewAsset.AnimComposite = AnimComposite;
|
|
Database->AddAnimationAsset(FInstancedStruct::Make(NewAsset));
|
|
}
|
|
}
|
|
|
|
void FDatabaseViewModel::AddAnimMontageToDatabase(UAnimMontage* AnimMontage)
|
|
{
|
|
if (UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
Database->Modify();
|
|
|
|
FPoseSearchDatabaseAnimMontage NewAsset;
|
|
NewAsset.AnimMontage = AnimMontage;
|
|
Database->AddAnimationAsset(FInstancedStruct::Make(NewAsset));
|
|
}
|
|
}
|
|
|
|
void FDatabaseViewModel::AddMultiAnimAssetToDatabase(UMultiAnimAsset* MultiAnimAsset)
|
|
{
|
|
if (UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
Database->Modify();
|
|
|
|
FPoseSearchDatabaseMultiAnimAsset NewAsset;
|
|
NewAsset.MultiAnimAsset = MultiAnimAsset;
|
|
Database->AddAnimationAsset(FInstancedStruct::Make(NewAsset));
|
|
}
|
|
}
|
|
|
|
bool FDatabaseViewModel::DeleteFromDatabase(int32 AnimationAssetIndex)
|
|
{
|
|
if (UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (const FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAssetBase = Database->GetDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(AnimationAssetIndex))
|
|
{
|
|
Database->Modify();
|
|
|
|
if (DatabaseAnimationAssetBase->IsSynchronizedWithExternalDependency())
|
|
{
|
|
if (UAnimSequenceBase* AnimSequenceBase = Cast<UAnimSequenceBase>(DatabaseAnimationAssetBase->GetAnimationAsset()))
|
|
{
|
|
bool bModified = false;
|
|
for (int32 NotifyIndex = AnimSequenceBase->Notifies.Num() - 1; NotifyIndex >= 0; --NotifyIndex)
|
|
{
|
|
const FAnimNotifyEvent& NotifyEvent = AnimSequenceBase->Notifies[NotifyIndex];
|
|
if (NotifyEvent.NotifyStateClass && NotifyEvent.NotifyStateClass->GetClass()->IsChildOf<UAnimNotifyState_PoseSearchBranchIn>())
|
|
{
|
|
const UAnimNotifyState_PoseSearchBranchIn* PoseSearchBranchIn = Cast<UAnimNotifyState_PoseSearchBranchIn>(NotifyEvent.NotifyStateClass);
|
|
check(PoseSearchBranchIn);
|
|
|
|
if (PoseSearchBranchIn->Database == Database && PoseSearchBranchIn->GetBranchInId() == DatabaseAnimationAssetBase->BranchInId)
|
|
{
|
|
if (!bModified)
|
|
{
|
|
AnimSequenceBase->Modify();
|
|
bModified = true;
|
|
}
|
|
|
|
AnimSequenceBase->Notifies.RemoveAt(NotifyIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bModified)
|
|
{
|
|
AnimSequenceBase->RefreshCacheData();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogPoseSearchEditor, Error, TEXT("found DatabaseAnimationAssetBase with valid BranchInId, but invalid AnimSequenceBase in %s"), *Database->GetName());
|
|
}
|
|
}
|
|
|
|
Database->RemoveAnimationAssetAt(AnimationAssetIndex);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FDatabaseViewModel::SetDisableReselection(int32 AnimationAssetIndex, bool bEnabled)
|
|
{
|
|
if (UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = Database->GetMutableDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(AnimationAssetIndex))
|
|
{
|
|
Database->Modify();
|
|
|
|
DatabaseAnimationAsset->SetDisableReselection(bEnabled);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FDatabaseViewModel::IsDisableReselection(int32 AnimationAssetIndex) const
|
|
{
|
|
if (const UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (const FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = Database->GetDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(AnimationAssetIndex))
|
|
{
|
|
return DatabaseAnimationAsset->IsDisableReselection();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FDatabaseViewModel::SetIsEnabled(int32 AnimationAssetIndex, bool bEnabled)
|
|
{
|
|
if (UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = Database->GetMutableDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(AnimationAssetIndex))
|
|
{
|
|
Database->Modify();
|
|
|
|
DatabaseAnimationAsset->SetIsEnabled(bEnabled);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FDatabaseViewModel::IsEnabled(int32 AnimationAssetIndex) const
|
|
{
|
|
if (const UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (const FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = Database->GetDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(AnimationAssetIndex))
|
|
{
|
|
return DatabaseAnimationAsset->IsEnabled();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FDatabaseViewModel::SetAnimationAsset(int32 AnimationAssetIndex, UObject* AnimAsset)
|
|
{
|
|
if (AnimAsset)
|
|
{
|
|
if (UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = Database->GetMutableDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(AnimationAssetIndex))
|
|
{
|
|
// Ensure that our target database item matches the input object's class.
|
|
const UClass* AssetClass = AnimAsset->GetClass();
|
|
if (AssetClass->IsChildOf(DatabaseAnimationAsset->GetAnimationAssetStaticClass()))
|
|
{
|
|
if (AssetClass->IsChildOf( UAnimSequence::StaticClass()))
|
|
{
|
|
Database->Modify();
|
|
FPoseSearchDatabaseSequence* DatabaseSequenceAsset = static_cast<FPoseSearchDatabaseSequence*>(DatabaseAnimationAsset);
|
|
DatabaseSequenceAsset->Sequence = Cast<UAnimSequence>(AnimAsset);
|
|
return true;
|
|
}
|
|
|
|
if (AssetClass->IsChildOf(UAnimComposite::StaticClass()))
|
|
{
|
|
Database->Modify();
|
|
FPoseSearchDatabaseAnimComposite* DatabaseCompositeAsset = static_cast<FPoseSearchDatabaseAnimComposite*>(DatabaseAnimationAsset);
|
|
DatabaseCompositeAsset->AnimComposite = Cast<UAnimComposite>(AnimAsset);
|
|
return true;
|
|
}
|
|
|
|
if (AssetClass->IsChildOf(UAnimMontage::StaticClass()))
|
|
{
|
|
Database->Modify();
|
|
FPoseSearchDatabaseAnimMontage* DatabaseMontageAsset = static_cast<FPoseSearchDatabaseAnimMontage*>(DatabaseAnimationAsset);
|
|
DatabaseMontageAsset->AnimMontage = Cast<UAnimMontage>(AnimAsset);
|
|
return true;
|
|
}
|
|
|
|
if (AssetClass->IsChildOf(UBlendSpace::StaticClass()))
|
|
{
|
|
Database->Modify();
|
|
FPoseSearchDatabaseBlendSpace* DatabaseBlendSpaceAsset = static_cast<FPoseSearchDatabaseBlendSpace*>(DatabaseAnimationAsset);
|
|
DatabaseBlendSpaceAsset->BlendSpace = Cast<UBlendSpace>(AnimAsset);
|
|
return true;
|
|
}
|
|
|
|
if (AssetClass->IsChildOf(UMultiAnimAsset::StaticClass()))
|
|
{
|
|
Database->Modify();
|
|
FPoseSearchDatabaseMultiAnimAsset* DatabaseMultiAnimAssetAsset = static_cast<FPoseSearchDatabaseMultiAnimAsset*>(DatabaseAnimationAsset);
|
|
DatabaseMultiAnimAssetAsset->MultiAnimAsset = Cast<UMultiAnimAsset>(AnimAsset);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FDatabaseViewModel::SetMirrorOption(int32 AnimationAssetIndex, EPoseSearchMirrorOption InMirrorOption)
|
|
{
|
|
if (UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = Database->GetMutableDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(AnimationAssetIndex))
|
|
{
|
|
Database->Modify();
|
|
|
|
DatabaseAnimationAsset->MirrorOption = InMirrorOption;
|
|
}
|
|
}
|
|
}
|
|
|
|
EPoseSearchMirrorOption FDatabaseViewModel::GetMirrorOption(int32 AnimationAssetIndex)
|
|
{
|
|
if (const UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (const FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = Database->GetDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(AnimationAssetIndex))
|
|
{
|
|
return DatabaseAnimationAsset->MirrorOption;
|
|
}
|
|
}
|
|
|
|
return EPoseSearchMirrorOption::MirroredOnly;
|
|
}
|
|
|
|
int32 FDatabaseViewModel::SetSelectedNode(int32 PoseIdx, bool bClearSelection, bool bDrawQuery, TConstArrayView<float> InQueryVector)
|
|
{
|
|
int32 SelectedSourceAssetIdx = INDEX_NONE;
|
|
|
|
if (bClearSelection)
|
|
{
|
|
RemovePreviewActors();
|
|
}
|
|
|
|
bIsEditorSelection = false;
|
|
bDrawQueryVector = bDrawQuery;
|
|
QueryVector = InQueryVector;
|
|
|
|
if (const UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (EAsyncBuildIndexResult::Success == FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(Database, ERequestAsyncBuildFlag::ContinueRequest))
|
|
{
|
|
const FSearchIndex& SearchIndex = Database->GetSearchIndex();
|
|
if (SearchIndex.PoseMetadata.IsValidIndex(PoseIdx))
|
|
{
|
|
const uint32 IndexAssetIndex = SearchIndex.PoseMetadata[PoseIdx].GetAssetIndex();
|
|
if (SearchIndex.Assets.IsValidIndex(IndexAssetIndex))
|
|
{
|
|
const FSearchIndexAsset& IndexAsset = SearchIndex.Assets[IndexAssetIndex];
|
|
const FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = Database->GetDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(IndexAsset.GetSourceAssetIdx());
|
|
check(DatabaseAnimationAsset);
|
|
int32 PreviewActorGroupIndex = INDEX_NONE;
|
|
for (int32 RoleIndex = 0; RoleIndex < DatabaseAnimationAsset->GetNumRoles(); ++RoleIndex)
|
|
{
|
|
FDatabasePreviewActor PreviewActor;
|
|
const UE::PoseSearch::FRole Role = DatabaseAnimationAsset->GetRole(RoleIndex);
|
|
const FTransform& RootTransformOrigin = DatabaseAnimationAsset->GetRootTransformOriginForRole(Role);
|
|
if (PreviewActor.SpawnPreviewActor(GetWorld(), Database, IndexAssetIndex, Role, RootTransformOrigin, PoseIdx))
|
|
{
|
|
if (PreviewActorGroupIndex == INDEX_NONE)
|
|
{
|
|
PreviewActorGroupIndex = PreviewActors.AddDefaulted();
|
|
}
|
|
|
|
MaxPreviewPlayLength = FMath::Max(MaxPreviewPlayLength, IndexAsset.GetLastSampleTime(Database->Schema->SampleRate) - PreviewActor.GetPlayTimeOffset());
|
|
MinPreviewPlayLength = FMath::Min(MinPreviewPlayLength, IndexAsset.GetFirstSampleTime(Database->Schema->SampleRate) - PreviewActor.GetPlayTimeOffset());
|
|
PreviewActors[PreviewActorGroupIndex].Add(MoveTemp(PreviewActor));
|
|
SelectedSourceAssetIdx = IndexAsset.GetSourceAssetIdx();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DatabaseDataDetails.Pin()->Reconstruct();
|
|
|
|
for (TArray<FDatabasePreviewActor>& PreviewActorGroup : PreviewActors)
|
|
{
|
|
for (FDatabasePreviewActor& PreviewActor : PreviewActorGroup)
|
|
{
|
|
PreviewActor.UpdatePreviewActor(Database, PlayTime, bQuantizeAnimationToPoseData);
|
|
}
|
|
}
|
|
|
|
SetPlayTime(0.f, false);
|
|
}
|
|
}
|
|
|
|
ProcessSelectedActor(nullptr);
|
|
|
|
return SelectedSourceAssetIdx;
|
|
}
|
|
|
|
void FDatabaseViewModel::SetSelectedNodes(const TArrayView<TSharedPtr<FDatabaseAssetTreeNode>>& InSelectedNodes)
|
|
{
|
|
RemovePreviewActors();
|
|
|
|
if (const UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (EAsyncBuildIndexResult::Success == FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(Database, ERequestAsyncBuildFlag::ContinueRequest))
|
|
{
|
|
TMap<int32, int32> AssociatedAssetIndices;
|
|
for (int32 i = 0; i < InSelectedNodes.Num(); ++i)
|
|
{
|
|
AssociatedAssetIndices.FindOrAdd(InSelectedNodes[i]->SourceAssetIdx) = i;
|
|
}
|
|
|
|
const FSearchIndex& SearchIndex = Database->GetSearchIndex();
|
|
for (int32 IndexAssetIndex = 0; IndexAssetIndex < SearchIndex.Assets.Num(); ++IndexAssetIndex)
|
|
{
|
|
const FSearchIndexAsset& IndexAsset = SearchIndex.Assets[IndexAssetIndex];
|
|
if (AssociatedAssetIndices.Find(IndexAsset.GetSourceAssetIdx()))
|
|
{
|
|
const FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAsset = Database->GetDatabaseAnimationAsset<FPoseSearchDatabaseAnimationAssetBase>(IndexAsset.GetSourceAssetIdx());
|
|
check(DatabaseAnimationAsset);
|
|
int32 PreviewActorGroupIndex = INDEX_NONE;
|
|
for (int32 RoleIndex = 0; RoleIndex < DatabaseAnimationAsset->GetNumRoles(); ++RoleIndex)
|
|
{
|
|
FDatabasePreviewActor PreviewActor;
|
|
const UE::PoseSearch::FRole Role = DatabaseAnimationAsset->GetRole(RoleIndex);
|
|
const FTransform RootTransformOrigin = DatabaseAnimationAsset->GetRootTransformOriginForRole(Role);
|
|
if (PreviewActor.SpawnPreviewActor(GetWorld(), Database, IndexAssetIndex, Role, RootTransformOrigin))
|
|
{
|
|
if (PreviewActorGroupIndex == INDEX_NONE)
|
|
{
|
|
PreviewActorGroupIndex = PreviewActors.AddDefaulted();
|
|
}
|
|
|
|
MaxPreviewPlayLength = FMath::Max(MaxPreviewPlayLength, IndexAsset.GetLastSampleTime(Database->Schema->SampleRate) - IndexAsset.GetFirstSampleTime(Database->Schema->SampleRate));
|
|
PreviewActors[PreviewActorGroupIndex].Add(MoveTemp(PreviewActor));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DatabaseDataDetails.Pin()->Reconstruct();
|
|
for (TArray<FDatabasePreviewActor>& PreviewActorGroup : PreviewActors)
|
|
{
|
|
for (FDatabasePreviewActor& PreviewActor : PreviewActorGroup)
|
|
{
|
|
PreviewActor.UpdatePreviewActor(Database, PlayTime, bQuantizeAnimationToPoseData);
|
|
}
|
|
}
|
|
}
|
|
|
|
ProcessSelectedActor(nullptr);
|
|
}
|
|
}
|
|
|
|
void FDatabaseViewModel::ProcessSelectedActor(AActor* Actor)
|
|
{
|
|
SelectedActorIndexAssetIndex = INDEX_NONE;
|
|
|
|
for (const TArray<FDatabasePreviewActor>& PreviewActorGroup : PreviewActors)
|
|
{
|
|
for (const FDatabasePreviewActor& PreviewActor : PreviewActorGroup)
|
|
{
|
|
if (PreviewActor.GetActor() == Actor)
|
|
{
|
|
SelectedActorIndexAssetIndex = PreviewActor.GetIndexAssetIndex();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDatabaseViewModel::SetDrawQueryVector(bool bValue)
|
|
{
|
|
if (bDrawQueryVector != bValue)
|
|
{
|
|
bDrawQueryVector = bValue;
|
|
DatabaseDataDetails.Pin()->Reconstruct();
|
|
}
|
|
}
|
|
|
|
const FSearchIndexAsset* FDatabaseViewModel::GetSelectedActorIndexAsset() const
|
|
{
|
|
if (SelectedActorIndexAssetIndex >= 0)
|
|
{
|
|
const UPoseSearchDatabase* Database = GetPoseSearchDatabase();
|
|
if (EAsyncBuildIndexResult::Success == FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(Database, ERequestAsyncBuildFlag::ContinueRequest))
|
|
{
|
|
const FSearchIndex& SearchIndex = Database->GetSearchIndex();
|
|
if (SearchIndex.Assets.IsValidIndex(SelectedActorIndexAssetIndex))
|
|
{
|
|
return &SearchIndex.Assets[SelectedActorIndexAssetIndex];
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TRange<double> FDatabaseViewModel::GetPreviewPlayRange() const
|
|
{
|
|
constexpr double ViewRangeSlack = 0.2;
|
|
return TRange<double>(MinPreviewPlayLength - ViewRangeSlack, MaxPreviewPlayLength + ViewRangeSlack);
|
|
}
|
|
|
|
float FDatabaseViewModel::GetPlayTime() const
|
|
{
|
|
return PlayTime;
|
|
}
|
|
|
|
void FDatabaseViewModel::SetPlayTime(float NewPlayTime, bool bInTickPlayTime)
|
|
{
|
|
PlayTime = FMath::Clamp(NewPlayTime, MinPreviewPlayLength, MaxPreviewPlayLength);
|
|
DeltaTimeMultiplier = bInTickPlayTime ? DeltaTimeMultiplier : 0.f;
|
|
|
|
if (const UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (EAsyncBuildIndexResult::Success == FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(Database, ERequestAsyncBuildFlag::ContinueRequest))
|
|
{
|
|
for (TArray<FDatabasePreviewActor>& PreviewActorGroup : PreviewActors)
|
|
{
|
|
for (FDatabasePreviewActor& PreviewActor : PreviewActorGroup)
|
|
{
|
|
PreviewActor.UpdatePreviewActor(Database, PlayTime, bQuantizeAnimationToPoseData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FDatabaseViewModel::GetAnimationTime(int32 SourceAssetIdx, float& CurrentPlayTime, FVector& BlendParameters) const
|
|
{
|
|
if (const UPoseSearchDatabase* Database = GetPoseSearchDatabase())
|
|
{
|
|
if (EAsyncBuildIndexResult::Success == FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(Database, ERequestAsyncBuildFlag::ContinueRequest))
|
|
{
|
|
const FSearchIndex& SearchIndex = Database->GetSearchIndex();
|
|
for (const TArray<FDatabasePreviewActor>& PreviewActorGroup : PreviewActors)
|
|
{
|
|
for (const FDatabasePreviewActor& PreviewActor : PreviewActorGroup)
|
|
{
|
|
if (PreviewActor.GetIndexAssetIndex() >= 0 && PreviewActor.GetIndexAssetIndex() < SearchIndex.Assets.Num())
|
|
{
|
|
const FSearchIndexAsset& IndexAsset = SearchIndex.Assets[PreviewActor.GetIndexAssetIndex()];
|
|
if (IndexAsset.GetSourceAssetIdx() == SourceAssetIdx)
|
|
{
|
|
CurrentPlayTime = PreviewActor.GetSampler().ToNormalizedTime(PlayTime + IndexAsset.GetFirstSampleTime(Database->Schema->SampleRate) + PreviewActor.GetPlayTimeOffset());
|
|
BlendParameters = IndexAsset.GetBlendParameters();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const FSearchIndexAsset& IndexAsset : SearchIndex.Assets)
|
|
{
|
|
if (IndexAsset.GetSourceAssetIdx() == SourceAssetIdx)
|
|
{
|
|
CurrentPlayTime = PlayTime + IndexAsset.GetFirstSampleTime(Database->Schema->SampleRate);
|
|
BlendParameters = IndexAsset.GetBlendParameters();
|
|
|
|
const bool bIsBlendSpace = Database->GetDatabaseAnimationAsset<FPoseSearchDatabaseBlendSpace>(IndexAsset) != nullptr;
|
|
if (bIsBlendSpace && !FMath::IsNearlyEqual(MaxPreviewPlayLength, MinPreviewPlayLength))
|
|
{
|
|
CurrentPlayTime = (CurrentPlayTime - MaxPreviewPlayLength) / (MaxPreviewPlayLength - MinPreviewPlayLength);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CurrentPlayTime = 0.f;
|
|
BlendParameters = FVector::ZeroVector;
|
|
return false;
|
|
}
|
|
}
|