1371 lines
53 KiB
C++
1371 lines
53 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PoseSearch/PoseSearchHistory.h"
|
|
#include "AnimationRuntime.h"
|
|
#include "Animation/AnimInstanceProxy.h"
|
|
#include "Animation/AnimNodeBase.h"
|
|
#include "Animation/AnimRootMotionProvider.h"
|
|
#include "Animation/SkeletonRemapping.h"
|
|
#include "Animation/SkeletonRemappingRegistry.h"
|
|
#include "BonePose.h"
|
|
#include "DrawDebugHelpers.h"
|
|
#include "Animation/TrajectoryTypes.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "PoseSearch/PoseSearchCustomVersion.h"
|
|
#include "PoseSearch/PoseSearchResult.h"
|
|
#include "PoseSearch/PoseSearchDatabase.h"
|
|
#include "PoseSearch/PoseSearchDefines.h"
|
|
|
|
IMPLEMENT_ANIMGRAPH_MESSAGE(UE::PoseSearch::FPoseHistoryProvider);
|
|
|
|
namespace UE::PoseSearch
|
|
{
|
|
|
|
#if ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUG
|
|
static bool GVarAnimPoseHistoryDebugDrawPose = false;
|
|
static FAutoConsoleVariableRef CVarAnimPoseHistoryDebugDrawPose(TEXT("a.AnimNode.PoseHistory.DebugDrawPose"), GVarAnimPoseHistoryDebugDrawPose, TEXT("Enable / Disable Pose History Pose DebugDraw"));
|
|
|
|
static bool GVarAnimPoseHistoryDebugDrawTrajectory = false;
|
|
static FAutoConsoleVariableRef CVarAnimPoseHistoryDebugDrawTrajectory(TEXT("a.AnimNode.PoseHistory.DebugDrawTrajectory"), GVarAnimPoseHistoryDebugDrawTrajectory, TEXT("Enable / Disable Pose History Trajectory DebugDraw"));
|
|
|
|
static float GVarAnimPoseHistoryDebugDrawTrajectoryThickness = 0.f;
|
|
static FAutoConsoleVariableRef CVarAnimPoseHistoryDebugDrawTrajectoryThickness(TEXT("a.AnimNode.PoseHistory.DebugDrawTrajectoryThickness"), GVarAnimPoseHistoryDebugDrawTrajectoryThickness, TEXT("Thickness of the trajectory debug draw (Default 0.0f)"));
|
|
|
|
static int32 GVarAnimPoseHistoryDebugDrawTrajectoryMaxNumOfHistorySamples = -1;
|
|
static FAutoConsoleVariableRef CVarAnimPoseHistoryDebugDrawTrajectoryMaxNumOfHistorySamples(TEXT("a.AnimNode.PoseHistory.DebugDrawMaxNumOfHistorySamples"), GVarAnimPoseHistoryDebugDrawTrajectoryMaxNumOfHistorySamples, TEXT("Max number of history samples to debug draw. All history samples will be drawn if value is negative. (Default -1)"));
|
|
|
|
static int32 GVarAnimPoseHistoryDebugDrawTrajectoryMaxNumOfPredictionSamples = -1;
|
|
static FAutoConsoleVariableRef CVarAnimPoseHistoryDebugDrawTrajectoryMaxNumOfPredictionSamples(TEXT("a.AnimNode.PoseHistory.DebugDrawMaxNumOfPredictionSamples"), GVarAnimPoseHistoryDebugDrawTrajectoryMaxNumOfPredictionSamples, TEXT("Max number of prediction samples to debug draw. All prediction samples will be drawn if value is negative. (Default -1)"));
|
|
#endif
|
|
|
|
/**
|
|
* Algo::LowerBound adapted to TIndexedContainerIterator for use with indexable but not necessarily contiguous containers. Used here with TRingBuffer.
|
|
*
|
|
* Performs binary search, resulting in position of the first element >= Value using predicate
|
|
*
|
|
* @param First TIndexedContainerIterator beginning of range to search through, must be already sorted by SortPredicate
|
|
* @param Last TIndexedContainerIterator end of range
|
|
* @param Value Value to look for
|
|
* @param SortPredicate Predicate for sort comparison, defaults to <
|
|
*
|
|
* @returns Position of the first element >= Value, may be position after last element in range
|
|
*/
|
|
template <typename IteratorType, typename ValueType, typename SortPredicateType = TLess<>()>
|
|
FORCEINLINE auto LowerBound(IteratorType First, IteratorType Last, const ValueType& Value, SortPredicateType SortPredicate) -> decltype(First.GetIndex())
|
|
{
|
|
using SizeType = decltype(First.GetIndex());
|
|
|
|
check(First.GetIndex() <= Last.GetIndex());
|
|
|
|
// Current start of sequence to check
|
|
SizeType Start = First.GetIndex();
|
|
|
|
// Size of sequence to check
|
|
SizeType Size = Last.GetIndex() - Start;
|
|
|
|
// With this method, if Size is even it will do one more comparison than necessary, but because Size can be predicted by the CPU it is faster in practice
|
|
while (Size > 0)
|
|
{
|
|
const SizeType LeftoverSize = Size % 2;
|
|
Size = Size / 2;
|
|
|
|
const SizeType CheckIndex = Start + Size;
|
|
const SizeType StartIfLess = CheckIndex + LeftoverSize;
|
|
Start = SortPredicate(*(First + CheckIndex), Value) ? StartIfLess : Start;
|
|
}
|
|
return Start;
|
|
}
|
|
|
|
static FBoneIndexType GetRemappedBoneIndexType(FBoneIndexType BoneIndexType, const USkeleton* BoneIndexSkeleton, const USkeleton* LastUpdateSkeleton)
|
|
{
|
|
// remapping the skeleton bone index (encoded as BoneIndexType) in case the skeleton we used to store the history (LastUpdateSkeleton) is different from BoneIndexSkeleton
|
|
if (LastUpdateSkeleton != nullptr && LastUpdateSkeleton != BoneIndexSkeleton)
|
|
{
|
|
const FSkeletonRemapping& SkeletonRemapping = UE::Anim::FSkeletonRemappingRegistry::Get().GetRemapping(BoneIndexSkeleton, LastUpdateSkeleton);
|
|
if (SkeletonRemapping.IsValid())
|
|
{
|
|
BoneIndexType = SkeletonRemapping.GetTargetSkeletonBoneIndex(BoneIndexType);
|
|
}
|
|
}
|
|
|
|
return BoneIndexType;
|
|
}
|
|
|
|
static FComponentSpaceTransformIndex GetRemappedComponentSpaceTransformIndex(const USkeleton* BoneIndexSkeleton, const USkeleton* LastUpdateSkeleton, const FBoneToTransformMap& BoneToTransformMap, FBoneIndexType BoneIndexType, bool& bSuccess)
|
|
{
|
|
check(BoneIndexType != WorldSpaceIndexType);
|
|
|
|
FComponentSpaceTransformIndex BoneTransformIndex = FComponentSpaceTransformIndex(BoneIndexType);
|
|
if (BoneTransformIndex != ComponentSpaceIndexType)
|
|
{
|
|
BoneTransformIndex = GetRemappedBoneIndexType(BoneTransformIndex, BoneIndexSkeleton, LastUpdateSkeleton);
|
|
|
|
if (!BoneToTransformMap.IsEmpty())
|
|
{
|
|
if (const FComponentSpaceTransformIndex* FoundBoneTransformIndex = BoneToTransformMap.Find(BoneTransformIndex))
|
|
{
|
|
BoneTransformIndex = *FoundBoneTransformIndex;
|
|
}
|
|
else
|
|
{
|
|
BoneTransformIndex = RootBoneIndexType;
|
|
bSuccess = false;
|
|
}
|
|
}
|
|
}
|
|
return BoneTransformIndex;
|
|
}
|
|
|
|
static bool LerpEntries(float Time, bool bExtrapolate, const FPoseHistoryEntry& PrevEntry, const FPoseHistoryEntry& NextEntry, const FName& CurveName, const TConstArrayView<FName>& CollectedCurves, float& OutCurveValue)
|
|
{
|
|
bool bSuccess = true;
|
|
|
|
const int32 CurveIndex = CollectedCurves.Find(CurveName);
|
|
if (CurveIndex == INDEX_NONE)
|
|
{
|
|
OutCurveValue = 0.0f;
|
|
bSuccess = false;
|
|
}
|
|
else
|
|
{
|
|
const float Denominator = NextEntry.AccumulatedSeconds - PrevEntry.AccumulatedSeconds;
|
|
float LerpValue = 0.f;
|
|
if (!FMath::IsNearlyZero(Denominator))
|
|
{
|
|
const float Numerator = Time - PrevEntry.AccumulatedSeconds;
|
|
LerpValue = bExtrapolate ? Numerator / Denominator : FMath::Clamp(Numerator / Denominator, 0.f, 1.f);
|
|
}
|
|
|
|
if (FMath::IsNearlyZero(LerpValue, ZERO_ANIMWEIGHT_THRESH))
|
|
{
|
|
OutCurveValue = PrevEntry.GetCurveValue(CurveIndex);
|
|
}
|
|
else if (FMath::IsNearlyZero(LerpValue - 1.f, ZERO_ANIMWEIGHT_THRESH))
|
|
{
|
|
OutCurveValue = NextEntry.GetCurveValue(CurveIndex);
|
|
}
|
|
else
|
|
{
|
|
OutCurveValue = FMath::Lerp(PrevEntry.GetCurveValue(CurveIndex), NextEntry.GetCurveValue(CurveIndex), LerpValue);
|
|
}
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
static bool LerpEntries(float Time, bool bExtrapolate, const FPoseHistoryEntry& PrevEntry, const FPoseHistoryEntry& NextEntry, const USkeleton* BoneIndexSkeleton, const USkeleton* LastUpdateSkeleton,
|
|
const FBoneToTransformMap& BoneToTransformMap, FBoneIndexType BoneIndexType, FBoneIndexType ReferenceBoneIndexType, FTransform& OutBoneTransform)
|
|
{
|
|
check(BoneIndexType != ReferenceBoneIndexType);
|
|
|
|
bool bSuccess = true;
|
|
const FComponentSpaceTransformIndex BoneTransformIndex = GetRemappedComponentSpaceTransformIndex(BoneIndexSkeleton, LastUpdateSkeleton, BoneToTransformMap, BoneIndexType, bSuccess);
|
|
check(BoneTransformIndex != ComponentSpaceIndexType);
|
|
|
|
const float Denominator = NextEntry.AccumulatedSeconds - PrevEntry.AccumulatedSeconds;
|
|
float LerpValue = 0.f;
|
|
if (!FMath::IsNearlyZero(Denominator))
|
|
{
|
|
const float Numerator = Time - PrevEntry.AccumulatedSeconds;
|
|
LerpValue = bExtrapolate ? Numerator / Denominator : FMath::Clamp(Numerator / Denominator, 0.f, 1.f);
|
|
}
|
|
|
|
const FComponentSpaceTransformIndex ReferenceBoneTransformIndex = GetRemappedComponentSpaceTransformIndex(BoneIndexSkeleton, LastUpdateSkeleton, BoneToTransformMap, ReferenceBoneIndexType, bSuccess);
|
|
if (ReferenceBoneTransformIndex == ComponentSpaceIndexType)
|
|
{
|
|
if (FMath::IsNearlyZero(LerpValue, ZERO_ANIMWEIGHT_THRESH))
|
|
{
|
|
OutBoneTransform = PrevEntry.GetComponentSpaceTransform(BoneTransformIndex);
|
|
}
|
|
else if (FMath::IsNearlyZero(LerpValue - 1.f, ZERO_ANIMWEIGHT_THRESH))
|
|
{
|
|
OutBoneTransform = NextEntry.GetComponentSpaceTransform(BoneTransformIndex);
|
|
}
|
|
else
|
|
{
|
|
OutBoneTransform.Blend(
|
|
PrevEntry.GetComponentSpaceTransform(BoneTransformIndex),
|
|
NextEntry.GetComponentSpaceTransform(BoneTransformIndex),
|
|
LerpValue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (FMath::IsNearlyZero(LerpValue, ZERO_ANIMWEIGHT_THRESH))
|
|
{
|
|
OutBoneTransform = PrevEntry.GetComponentSpaceTransform(BoneTransformIndex) * PrevEntry.GetComponentSpaceTransform(ReferenceBoneTransformIndex).Inverse();
|
|
}
|
|
else if (FMath::IsNearlyZero(LerpValue - 1.f, ZERO_ANIMWEIGHT_THRESH))
|
|
{
|
|
OutBoneTransform = NextEntry.GetComponentSpaceTransform(BoneTransformIndex) * NextEntry.GetComponentSpaceTransform(ReferenceBoneTransformIndex).Inverse();
|
|
}
|
|
else
|
|
{
|
|
OutBoneTransform.Blend(
|
|
PrevEntry.GetComponentSpaceTransform(BoneTransformIndex) * PrevEntry.GetComponentSpaceTransform(ReferenceBoneTransformIndex).Inverse(),
|
|
NextEntry.GetComponentSpaceTransform(BoneTransformIndex) * NextEntry.GetComponentSpaceTransform(ReferenceBoneTransformIndex).Inverse(),
|
|
LerpValue);
|
|
}
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
static uint32 GetTypeHash(const FBoneToTransformMap& BoneToTransformMap)
|
|
{
|
|
const int32 Num = BoneToTransformMap.Num();
|
|
|
|
if (Num == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
TArrayView<FBoneToTransformPair> Pairs((FBoneToTransformPair*)FMemory_Alloca(Num * sizeof(FBoneToTransformPair)), Num);
|
|
|
|
int32 Index = 0;
|
|
for (const FBoneToTransformPair& BoneToTransformPair : BoneToTransformMap)
|
|
{
|
|
Pairs[Index] = BoneToTransformPair;
|
|
++Index;
|
|
}
|
|
|
|
Pairs.StableSort();
|
|
|
|
uint32 TypeHash = ::GetTypeHash(Pairs[0]);
|
|
for (Index = 1; Index < Num; ++Index)
|
|
{
|
|
TypeHash = HashCombineFast(TypeHash, ::GetTypeHash(Pairs[Index]));
|
|
}
|
|
|
|
return TypeHash;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FComponentSpacePoseProvider
|
|
FComponentSpacePoseProvider::FComponentSpacePoseProvider(FCSPose<FCompactPose>& InComponentSpacePose)
|
|
: ComponentSpacePose(InComponentSpacePose)
|
|
{
|
|
check(GetSkeletonAsset());
|
|
}
|
|
|
|
FTransform FComponentSpacePoseProvider::CalculateComponentSpaceTransform(const FSkeletonPoseBoneIndex SkeletonBoneIdx)
|
|
{
|
|
const FBoneContainer& BoneContainer = ComponentSpacePose.GetPose().GetBoneContainer();
|
|
const FCompactPoseBoneIndex CompactBoneIdx = BoneContainer.GetCompactPoseIndexFromSkeletonPoseIndex(SkeletonBoneIdx);
|
|
if (CompactBoneIdx.IsValid())
|
|
{
|
|
return ComponentSpacePose.GetComponentSpaceTransform(CompactBoneIdx);
|
|
}
|
|
|
|
// NoTe: this chunk of code is very unlikely to be called, but in case:
|
|
// @todo: cache any transform outside the domain of ComponentSpacePose if needed
|
|
// @todo: use the skeletal mesh reference pose instead of the one from the skeleton if needed
|
|
const USkeleton* Skeleton = BoneContainer.GetSkeletonAsset();
|
|
const FReferenceSkeleton& ReferenceSkeleton = Skeleton->GetReferenceSkeleton();
|
|
const int32 ParentIndex = ReferenceSkeleton.GetParentIndex(SkeletonBoneIdx.GetInt());
|
|
check(ParentIndex >= 0);
|
|
|
|
const TArray<FTransform>& RefBonePose = Skeleton->GetReferenceSkeleton().GetRefBonePose();
|
|
return RefBonePose[SkeletonBoneIdx.GetInt()] * CalculateComponentSpaceTransform(FSkeletonPoseBoneIndex(ParentIndex));
|
|
}
|
|
|
|
const USkeleton* FComponentSpacePoseProvider::GetSkeletonAsset() const
|
|
{
|
|
return ComponentSpacePose.GetPose().GetBoneContainer().GetSkeletonAsset();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FAIPComponentSpacePoseProvider
|
|
FAIPComponentSpacePoseProvider::FAIPComponentSpacePoseProvider(const FAnimInstanceProxy* InAnimInstanceProxy)
|
|
{
|
|
check(InAnimInstanceProxy);
|
|
// initializing PoseHistory with a ref pose at FAnimInstanceProxy location/facing
|
|
const FBoneContainer& BoneContainer = InAnimInstanceProxy->GetRequiredBones();
|
|
|
|
// BoneContainer can be invalid when recompiling ABP while PIE is running
|
|
if (BoneContainer.IsValid())
|
|
{
|
|
ComponentSpacePose.InitPose(&BoneContainer);
|
|
}
|
|
}
|
|
|
|
FTransform FAIPComponentSpacePoseProvider::CalculateComponentSpaceTransform(const FSkeletonPoseBoneIndex SkeletonBoneIdx)
|
|
{
|
|
// NoTe: calling GetBoneTransform on the mesh returns Identity on the first frame of simulation, so we use a different approach
|
|
// const FMeshPoseBoneIndex MeshPoseBoneIndex = AnimInstanceProxy->GetRequiredBones().GetMeshPoseIndexFromSkeletonPoseIndex(SkeletonBoneIdx);
|
|
// return AnimInstanceProxy->GetSkelMeshComponent()->GetBoneTransform(MeshPoseBoneIndex.GetInt(), FTransform::Identity);
|
|
|
|
if (!ComponentSpacePose.GetPose().IsValid())
|
|
{
|
|
return FTransform::Identity;
|
|
}
|
|
|
|
const FBoneContainer& BoneContainer = ComponentSpacePose.GetPose().GetBoneContainer();
|
|
const FCompactPoseBoneIndex CompactBoneIdx = BoneContainer.GetCompactPoseIndexFromSkeletonPoseIndex(SkeletonBoneIdx);
|
|
if (CompactBoneIdx.IsValid())
|
|
{
|
|
return ComponentSpacePose.GetComponentSpaceTransform(CompactBoneIdx);
|
|
}
|
|
|
|
// NoTe: this chunk of code is very unlikely to be called, but in case:
|
|
// @todo: cache any transform outside the domain of ComponentSpacePose if needed
|
|
// @todo: use the skeletal mesh reference pose instead of the one from the skeleton if needed
|
|
const USkeleton* Skeleton = BoneContainer.GetSkeletonAsset();
|
|
const FReferenceSkeleton& ReferenceSkeleton = Skeleton->GetReferenceSkeleton();
|
|
const int32 ParentIndex = ReferenceSkeleton.GetParentIndex(SkeletonBoneIdx.GetInt());
|
|
check(ParentIndex >= 0);
|
|
|
|
const TArray<FTransform>& RefBonePose = Skeleton->GetReferenceSkeleton().GetRefBonePose();
|
|
return RefBonePose[SkeletonBoneIdx.GetInt()] * CalculateComponentSpaceTransform(FSkeletonPoseBoneIndex(ParentIndex));
|
|
|
|
}
|
|
|
|
const USkeleton* FAIPComponentSpacePoseProvider::GetSkeletonAsset() const
|
|
{
|
|
// return AnimInstanceProxy->GetSkelMeshComponent()->GetSkeletalMeshAsset()->GetSkeleton();
|
|
if (ComponentSpacePose.GetPose().IsValid())
|
|
{
|
|
return ComponentSpacePose.GetPose().GetBoneContainer().GetSkeletonAsset();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FPoseHistoryEntry
|
|
void FPoseHistoryEntry::Update(float Time, IComponentSpacePoseProvider& ComponentSpacePoseProvider, const FBoneToTransformMap& BoneToTransformMap, bool bStoreScales, const FBlendedCurve& Curves, const TConstArrayView<FName>& CollectedCurves)
|
|
{
|
|
AccumulatedSeconds = Time;
|
|
|
|
const USkeleton* Skeleton = ComponentSpacePoseProvider.GetSkeletonAsset();
|
|
check(Skeleton);
|
|
const int32 NumSkeletonBones = Skeleton->GetReferenceSkeleton().GetNum();
|
|
if (BoneToTransformMap.IsEmpty())
|
|
{
|
|
// no mapping: we add all the transforms
|
|
SetNum(NumSkeletonBones, bStoreScales);
|
|
for (FSkeletonPoseBoneIndex SkeletonBoneIdx(0); SkeletonBoneIdx != NumSkeletonBones; ++SkeletonBoneIdx)
|
|
{
|
|
SetComponentSpaceTransform(SkeletonBoneIdx.GetInt(), ComponentSpacePoseProvider.CalculateComponentSpaceTransform(SkeletonBoneIdx));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetNum(BoneToTransformMap.Num(), true);
|
|
for (const FBoneToTransformPair& BoneToTransformPair : BoneToTransformMap)
|
|
{
|
|
const FSkeletonPoseBoneIndex SkeletonBoneIdx(BoneToTransformPair.Key);
|
|
SetComponentSpaceTransform(BoneToTransformPair.Value, ComponentSpacePoseProvider.CalculateComponentSpaceTransform(SkeletonBoneIdx));
|
|
}
|
|
}
|
|
|
|
const int32 NumCurves = CollectedCurves.Num();
|
|
CurveValues.SetNum(NumCurves);
|
|
for (int i = 0; i < NumCurves; ++i)
|
|
{
|
|
const FName& CurveName = CollectedCurves[i];
|
|
CurveValues[i] = Curves.Get(CurveName);
|
|
}
|
|
}
|
|
|
|
void FPoseHistoryEntry::SetNum(int32 Num, bool bStoreScales)
|
|
{
|
|
ComponentSpaceRotations.SetNum(Num);
|
|
ComponentSpacePositions.SetNum(Num);
|
|
ComponentSpaceScales.SetNum(bStoreScales ? Num : 0);
|
|
}
|
|
|
|
int32 FPoseHistoryEntry::Num() const
|
|
{
|
|
return ComponentSpaceRotations.Num();
|
|
}
|
|
|
|
void FPoseHistoryEntry::SetComponentSpaceTransform(int32 Index, const FTransform& Transform)
|
|
{
|
|
check( Transform.IsRotationNormalized() );
|
|
ComponentSpaceRotations[Index] = FQuat4f(Transform.GetRotation());
|
|
ComponentSpacePositions[Index] = Transform.GetTranslation();
|
|
|
|
if (!ComponentSpaceScales.IsEmpty())
|
|
{
|
|
ComponentSpaceScales[Index] = FVector3f(Transform.GetScale3D());
|
|
}
|
|
}
|
|
|
|
FTransform FPoseHistoryEntry::GetComponentSpaceTransform(int32 Index) const
|
|
{
|
|
if (ComponentSpaceRotations.IsValidIndex(Index))
|
|
{
|
|
check(ComponentSpacePositions.Num() == ComponentSpaceRotations.Num());
|
|
check(ComponentSpaceScales.IsEmpty() || ComponentSpaceRotations.Num() == ComponentSpaceScales.Num());
|
|
|
|
const FQuat Quat(ComponentSpaceRotations[Index]);
|
|
const FVector Scale(ComponentSpaceScales.IsEmpty() ? FVector3f::OneVector : ComponentSpaceScales[Index]);
|
|
return FTransform(Quat, ComponentSpacePositions[Index], Scale);
|
|
}
|
|
|
|
UE_LOG(LogPoseSearch, Error, TEXT("FPoseHistoryEntry::GetComponentSpaceTransform - Index %d out of bound [0, %d)"), Index, ComponentSpaceRotations.Num());
|
|
return FTransform::Identity;
|
|
}
|
|
|
|
float FPoseHistoryEntry::GetCurveValue(int32 Index) const
|
|
{
|
|
if (CurveValues.IsValidIndex(Index))
|
|
{
|
|
return CurveValues[Index];
|
|
}
|
|
|
|
UE_LOG(LogPoseSearch, Error, TEXT("FPoseHistoryEntry::GetCurveValue - Index %d out of bound [0, %d)"), Index, CurveValues.Num());
|
|
return 0.0f;
|
|
}
|
|
|
|
FArchive& operator<<(FArchive& Ar, FPoseHistoryEntry& Entry)
|
|
{
|
|
Ar << Entry.ComponentSpaceRotations;
|
|
Ar << Entry.ComponentSpacePositions;
|
|
Ar << Entry.ComponentSpaceScales;
|
|
Ar << Entry.CurveValues;
|
|
Ar << Entry.AccumulatedSeconds;
|
|
return Ar;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// IPoseHistory
|
|
#if ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUG
|
|
void IPoseHistory::DebugDraw(FAnimInstanceProxy& AnimInstanceProxy, FColor Color, float Time, float PointSize, bool bExtrapolate) const
|
|
{
|
|
const FBoneContainer& BoneContainer = AnimInstanceProxy.GetRequiredBones();
|
|
if (Color.A > 0 && BoneContainer.IsValid())
|
|
{
|
|
const USkeleton* Skeleton = BoneContainer.GetSkeletonAsset();
|
|
check(Skeleton);
|
|
|
|
FTransform OutBoneTransform;
|
|
|
|
const FBoneToTransformMap& BoneToTransformMap = GetBoneToTransformMap();
|
|
if (BoneToTransformMap.IsEmpty())
|
|
{
|
|
const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton();
|
|
const int32 NumSkeletonBones = RefSkeleton.GetNum();
|
|
for (FSkeletonPoseBoneIndex SkeletonBoneIdx(0); SkeletonBoneIdx != NumSkeletonBones; ++SkeletonBoneIdx)
|
|
{
|
|
if (GetTransformAtTime(Time, OutBoneTransform, Skeleton, SkeletonBoneIdx.GetInt(), WorldSpaceIndexType, bExtrapolate))
|
|
{
|
|
AnimInstanceProxy.AnimDrawDebugPoint(OutBoneTransform.GetTranslation(), PointSize, Color, false, 0.f, ESceneDepthPriorityGroup::SDPG_Foreground);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const FBoneToTransformPair& BoneToTransformPair : BoneToTransformMap)
|
|
{
|
|
const FSkeletonPoseBoneIndex SkeletonBoneIdx(BoneToTransformPair.Key);
|
|
if (GetTransformAtTime(Time, OutBoneTransform, Skeleton, SkeletonBoneIdx.GetInt(), WorldSpaceIndexType, bExtrapolate))
|
|
{
|
|
AnimInstanceProxy.AnimDrawDebugPoint(OutBoneTransform.GetTranslation(), PointSize, Color, false, 0.f, ESceneDepthPriorityGroup::SDPG_Foreground);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUG
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FArchivedPoseHistory
|
|
void FArchivedPoseHistory::InitFrom(const IPoseHistory* PoseHistory)
|
|
{
|
|
Trajectory.Samples.Reset();
|
|
BoneToTransformMap.Reset();
|
|
Entries.Reset();
|
|
|
|
if (PoseHistory)
|
|
{
|
|
Trajectory = PoseHistory->GetTrajectory();
|
|
BoneToTransformMap = PoseHistory->GetBoneToTransformMap();
|
|
CollectedCurves = PoseHistory->GetCollectedCurves();
|
|
const int32 NumEntries = PoseHistory->GetNumEntries();
|
|
Entries.SetNum(NumEntries);
|
|
|
|
for (int32 EntryIndex = 0; EntryIndex < NumEntries; ++EntryIndex)
|
|
{
|
|
Entries[EntryIndex] = PoseHistory->GetEntry(EntryIndex);
|
|
// validating input PoseHistory to have Entries properly sorted by time
|
|
check(EntryIndex == 0 || (Entries[EntryIndex - 1].AccumulatedSeconds <= Entries[EntryIndex].AccumulatedSeconds));
|
|
}
|
|
}
|
|
}
|
|
|
|
// in here FBoneIndexType BoneIndexType is a skeleton bone index, and it's used to dereference a BoneToTransformMap (skeleton bone index -> pose history bone index)
|
|
bool FArchivedPoseHistory::GetTransformAtTime(float Time, FTransform& OutBoneTransform, const USkeleton* BoneIndexSkeleton, FBoneIndexType BoneIndexType, FBoneIndexType ReferenceBoneIndexType, bool bExtrapolate) const
|
|
{
|
|
static_assert(RootBoneIndexType == 0 && ComponentSpaceIndexType == FBoneIndexType(-1) && WorldSpaceIndexType == FBoneIndexType(-2)); // some assumptions
|
|
|
|
if (BoneIndexType == ReferenceBoneIndexType)
|
|
{
|
|
OutBoneTransform = FTransform::Identity;
|
|
return true;
|
|
}
|
|
|
|
if (ReferenceBoneIndexType == WorldSpaceIndexType)
|
|
{
|
|
if (BoneIndexType == ComponentSpaceIndexType)
|
|
{
|
|
OutBoneTransform = Trajectory.GetSampleAtTime(Time, bExtrapolate).GetTransform();
|
|
return true;
|
|
}
|
|
|
|
// getting BoneIndexType in ComponentSpaceIndexType and then multiplying it with the component to world transform from the trajectory
|
|
const bool bSuccess = GetTransformAtTime(Time, OutBoneTransform, BoneIndexSkeleton, BoneIndexType, ComponentSpaceIndexType, bExtrapolate);
|
|
OutBoneTransform *= Trajectory.GetSampleAtTime(Time, bExtrapolate).GetTransform();
|
|
return bSuccess;
|
|
}
|
|
|
|
if (BoneIndexType == WorldSpaceIndexType || BoneIndexType == ComponentSpaceIndexType)
|
|
{
|
|
const bool bSuccess = GetTransformAtTime(Time, OutBoneTransform, BoneIndexSkeleton, ReferenceBoneIndexType, BoneIndexType, bExtrapolate);
|
|
OutBoneTransform = OutBoneTransform.Inverse();
|
|
return bSuccess;
|
|
}
|
|
|
|
check(BoneIndexType != ComponentSpaceIndexType && BoneIndexType != WorldSpaceIndexType && ReferenceBoneIndexType != WorldSpaceIndexType);
|
|
|
|
const int32 NumEntries = Entries.Num();
|
|
if (NumEntries > 0)
|
|
{
|
|
int32 NextIdx = 0;
|
|
int32 PrevIdx = 0;
|
|
|
|
if (NumEntries > 1)
|
|
{
|
|
const int32 LowerBoundIdx = Algo::LowerBound(Entries, Time, [](const FPoseHistoryEntry& Entry, float Value) { return Value > Entry.AccumulatedSeconds; });
|
|
NextIdx = FMath::Clamp(LowerBoundIdx, 1, NumEntries - 1);
|
|
PrevIdx = NextIdx - 1;
|
|
}
|
|
|
|
const FPoseHistoryEntry& PrevEntry = Entries[PrevIdx];
|
|
const FPoseHistoryEntry& NextEntry = Entries[NextIdx];
|
|
|
|
return LerpEntries(Time, bExtrapolate, PrevEntry, NextEntry, BoneIndexSkeleton, nullptr, BoneToTransformMap, BoneIndexType, ReferenceBoneIndexType, OutBoneTransform);
|
|
}
|
|
|
|
OutBoneTransform = FTransform::Identity;
|
|
return false;
|
|
}
|
|
|
|
bool FArchivedPoseHistory::GetCurveValueAtTime(float Time, const FName& CurveName, float& OutCurveValue, bool bExtrapolate /*= false*/) const
|
|
{
|
|
bool bSuccess = false;
|
|
|
|
const int32 NumEntries = Entries.Num();
|
|
if (NumEntries > 0)
|
|
{
|
|
int32 NextIdx = 0;
|
|
int32 PrevIdx = 0;
|
|
|
|
if (NumEntries > 1)
|
|
{
|
|
const int32 LowerBoundIdx = Algo::LowerBound(Entries, Time, [](const FPoseHistoryEntry& Entry, float Value) { return Value > Entry.AccumulatedSeconds; });
|
|
NextIdx = FMath::Clamp(LowerBoundIdx, 1, NumEntries - 1);
|
|
PrevIdx = NextIdx - 1;
|
|
}
|
|
|
|
const FPoseHistoryEntry& PrevEntry = Entries[PrevIdx];
|
|
const FPoseHistoryEntry& NextEntry = Entries[NextIdx];
|
|
|
|
bSuccess = LerpEntries(Time, bExtrapolate, PrevEntry, NextEntry, CurveName, GetCollectedCurves(), OutCurveValue);
|
|
}
|
|
else
|
|
{
|
|
OutCurveValue = 0.0f;
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
#if ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUG
|
|
void FArchivedPoseHistory::DebugDraw(const UWorld* World, FColor Color) const
|
|
{
|
|
if (Color.A > 0 && !Trajectory.Samples.IsEmpty())
|
|
{
|
|
TArray<FTransform, TInlineAllocator<128>> PrevGlobalTransforms;
|
|
|
|
for (int32 EntryIndex = 0; EntryIndex < Entries.Num(); ++EntryIndex)
|
|
{
|
|
const FPoseHistoryEntry& Entry = Entries[EntryIndex];
|
|
|
|
const int32 PrevGlobalTransformsNum = PrevGlobalTransforms.Num();
|
|
const int32 Max = FMath::Max(PrevGlobalTransformsNum, Entry.Num());
|
|
|
|
PrevGlobalTransforms.SetNum(Max, EAllowShrinking::No);
|
|
|
|
const bool bIsCurrentTimeEntry = FMath::IsNearlyZero(Entry.AccumulatedSeconds);
|
|
|
|
for (int32 i = 0; i < Entry.Num(); ++i)
|
|
{
|
|
const FTransform RootTransform = Trajectory.GetSampleAtTime(Entry.AccumulatedSeconds).GetTransform();
|
|
const FTransform GlobalTransforms = Entry.GetComponentSpaceTransform(i) * RootTransform;
|
|
|
|
if (i < PrevGlobalTransformsNum)
|
|
{
|
|
DrawDebugLine(World, PrevGlobalTransforms[i].GetTranslation(), GlobalTransforms.GetTranslation(), Color, false, -1.f, SDPG_Foreground);
|
|
}
|
|
|
|
if (bIsCurrentTimeEntry)
|
|
{
|
|
DrawDebugPoint(World, GlobalTransforms.GetTranslation(), 6.f, Color, false, -1.f, SDPG_Foreground);
|
|
|
|
if (i == 0)
|
|
{
|
|
DrawDebugLine(World, GlobalTransforms.GetTranslation(), GlobalTransforms.GetTranslation() + RootTransform.GetUnitAxis(EAxis::Type::X) * 25.f, FColor::Black, false, -1.f, SDPG_Foreground);
|
|
DrawDebugLine(World, GlobalTransforms.GetTranslation(), GlobalTransforms.GetTranslation() + GlobalTransforms.GetUnitAxis(EAxis::Type::X) * 20.f, FColor::White, false, -1.f, SDPG_Foreground);
|
|
}
|
|
}
|
|
|
|
if (i == 0)
|
|
{
|
|
DrawDebugLine(World, GlobalTransforms.GetTranslation(), RootTransform.GetTranslation(), FColor::Purple, false, -1.f, SDPG_Foreground);
|
|
}
|
|
|
|
PrevGlobalTransforms[i] = GlobalTransforms;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUG
|
|
|
|
FArchive& operator<<(FArchive& Ar, FArchivedPoseHistory& Entry)
|
|
{
|
|
Ar << Entry.BoneToTransformMap;
|
|
Ar << Entry.CollectedCurves;
|
|
Ar << Entry.Entries;
|
|
|
|
// Convert old FPoseSearchTrajectory to new FTransformTrajectory type at load time.
|
|
if (Ar.CustomVer(FPoseSearchCustomVersion::GUID) < FPoseSearchCustomVersion::DeprecatedTrajectoryTypes)
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
FPoseSearchQueryTrajectory OldTrajectoryType;
|
|
Ar << OldTrajectoryType;
|
|
Entry.Trajectory = OldTrajectoryType;
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
else
|
|
{
|
|
Ar << Entry.Trajectory;
|
|
}
|
|
|
|
return Ar;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FPoseHistory
|
|
FPoseHistory::FPoseHistory(const FPoseHistory& Other)
|
|
: FPoseHistory()
|
|
{
|
|
*this = Other;
|
|
}
|
|
|
|
FPoseHistory::FPoseHistory(FPoseHistory&& Other)
|
|
: FPoseHistory()
|
|
{
|
|
*this = MoveTemp(Other);
|
|
}
|
|
|
|
FPoseHistory& FPoseHistory::operator=(const FPoseHistory& Other)
|
|
{
|
|
if (this != &Other)
|
|
{
|
|
#if ENABLE_ANIM_DEBUG
|
|
UE_MT_SCOPED_WRITE_ACCESS(Other.PoseDataThreadSafeCounter);
|
|
UE_MT_SCOPED_WRITE_ACCESS(PoseDataThreadSafeCounter);
|
|
#endif // ENABLE_ANIM_DEBUG
|
|
|
|
MaxNumPoses = Other.MaxNumPoses;
|
|
SamplingInterval = Other.SamplingInterval;
|
|
|
|
Trajectory = Other.Trajectory;
|
|
TrajectoryDataState = Other.TrajectoryDataState;
|
|
TrajectorySpeedMultiplier = Other.TrajectorySpeedMultiplier;
|
|
|
|
PoseData = Other.PoseData;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
FPoseHistory& FPoseHistory::operator=(FPoseHistory&& Other)
|
|
{
|
|
if (this != &Other)
|
|
{
|
|
#if ENABLE_ANIM_DEBUG
|
|
UE_MT_SCOPED_WRITE_ACCESS(Other.PoseDataThreadSafeCounter);
|
|
UE_MT_SCOPED_WRITE_ACCESS(PoseDataThreadSafeCounter);
|
|
#endif // ENABLE_ANIM_DEBUG
|
|
|
|
MaxNumPoses = MoveTemp(Other.MaxNumPoses);
|
|
SamplingInterval = MoveTemp(Other.SamplingInterval);
|
|
|
|
Trajectory = MoveTemp(Other.Trajectory);
|
|
TrajectoryDataState = MoveTemp(Other.TrajectoryDataState);
|
|
TrajectorySpeedMultiplier = MoveTemp(Other.TrajectorySpeedMultiplier);
|
|
|
|
PoseData = MoveTemp(Other.PoseData);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void FPoseHistory::Initialize_AnyThread(int32 InNumPoses, float InSamplingInterval)
|
|
{
|
|
UE_MT_SCOPED_WRITE_ACCESS(PoseDataThreadSafeCounter);
|
|
check(InNumPoses >= 2);
|
|
|
|
MaxNumPoses = InNumPoses;
|
|
SamplingInterval = InSamplingInterval;
|
|
|
|
Trajectory = FTransformTrajectory();
|
|
TrajectoryDataState = FPoseSearchTrajectoryData::FState();
|
|
TrajectorySpeedMultiplier = 1.f;
|
|
|
|
PoseData = FPoseData();
|
|
}
|
|
|
|
bool FPoseHistory::GetTransformAtTime(float Time, FTransform& OutBoneTransform, const USkeleton* BoneIndexSkeleton, FBoneIndexType BoneIndexType, FBoneIndexType ReferenceBoneIndexType, bool bExtrapolate) const
|
|
{
|
|
UE_MT_SCOPED_READ_ACCESS(PoseDataThreadSafeCounter);
|
|
|
|
static_assert(RootBoneIndexType == 0 && ComponentSpaceIndexType == FBoneIndexType(-1) && WorldSpaceIndexType == FBoneIndexType(-2)); // some assumptions
|
|
|
|
bool bSuccess = true;
|
|
if (BoneIndexType == ReferenceBoneIndexType)
|
|
{
|
|
OutBoneTransform = FTransform::Identity;
|
|
bSuccess = true;
|
|
}
|
|
else if (ReferenceBoneIndexType == WorldSpaceIndexType)
|
|
{
|
|
if (BoneIndexType == ComponentSpaceIndexType)
|
|
{
|
|
OutBoneTransform = Trajectory.GetSampleAtTime(Time, bExtrapolate).GetTransform();
|
|
bSuccess = true;
|
|
}
|
|
else
|
|
{
|
|
// getting BoneIndexType in ComponentSpaceIndexType and then multiplying it with the component to world transform from the trajectory
|
|
bSuccess = GetTransformAtTime(Time, OutBoneTransform, BoneIndexSkeleton, BoneIndexType, ComponentSpaceIndexType, bExtrapolate);
|
|
OutBoneTransform *= Trajectory.GetSampleAtTime(Time, bExtrapolate).GetTransform();
|
|
}
|
|
}
|
|
else if (BoneIndexType == WorldSpaceIndexType || BoneIndexType == ComponentSpaceIndexType)
|
|
{
|
|
bSuccess = GetTransformAtTime(Time, OutBoneTransform, BoneIndexSkeleton, ReferenceBoneIndexType, BoneIndexType, bExtrapolate);
|
|
OutBoneTransform = OutBoneTransform.Inverse();
|
|
}
|
|
else
|
|
{
|
|
check(BoneIndexType != ComponentSpaceIndexType && BoneIndexType != WorldSpaceIndexType && ReferenceBoneIndexType != WorldSpaceIndexType);
|
|
|
|
const int32 NumEntries = PoseData.Entries.Num();
|
|
if (NumEntries > 0)
|
|
{
|
|
int32 NextIdx = 0;
|
|
int32 PrevIdx = 0;
|
|
|
|
if (NumEntries > 1)
|
|
{
|
|
const int32 LowerBoundIdx = LowerBound(PoseData.Entries.begin(), PoseData.Entries.end(), Time, [](const FPoseHistoryEntry& Entry, float Value) { return Value > Entry.AccumulatedSeconds; });
|
|
NextIdx = FMath::Clamp(LowerBoundIdx, 1, NumEntries - 1);
|
|
PrevIdx = NextIdx - 1;
|
|
}
|
|
|
|
const FPoseHistoryEntry& PrevEntry = PoseData.Entries[PrevIdx];
|
|
const FPoseHistoryEntry& NextEntry = PoseData.Entries[NextIdx];
|
|
|
|
bSuccess = LerpEntries(Time, bExtrapolate, PrevEntry, NextEntry, BoneIndexSkeleton, PoseData.LastUpdateSkeleton.Get(), PoseData.BoneToTransformMap, BoneIndexType, ReferenceBoneIndexType, OutBoneTransform);
|
|
}
|
|
else
|
|
{
|
|
OutBoneTransform = FTransform::Identity;
|
|
bSuccess = false;
|
|
}
|
|
}
|
|
|
|
// @todo: reenable this logging after implementing AnimNext PoseHistory initialization (currently spamming on actor spawning with MM active)
|
|
//#if WITH_EDITOR
|
|
// // @todo: logging errors only if pose history was properly initialized for now (since AnimNext version doesn't implement initialization with ref pose yet),
|
|
// // and we ultimately want to catch errors of missing bone transforms only checking valid USkeleton(s) against valid PoseSearchSchema(s)
|
|
// if (!bSuccess && BoneIndexSkeleton)
|
|
// {
|
|
// UE_LOG(LogPoseSearch, Error, TEXT("FPoseHistory::GetTransformAtTime - Couldn't find root bone transform at time %f for Skeleton %s!"), Time, *BoneIndexSkeleton->GetName());
|
|
// }
|
|
//#endif // WITH_EDITOR
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
bool FPoseHistory::GetCurveValueAtTime(float Time, const FName& CurveName, float& OutCurveValue, bool bExtrapolate /*= false*/) const
|
|
{
|
|
UE_MT_SCOPED_READ_ACCESS(PoseDataThreadSafeCounter);
|
|
|
|
const int32 NumEntries = PoseData.Entries.Num();
|
|
if (NumEntries > 0)
|
|
{
|
|
int32 NextIdx = 0;
|
|
int32 PrevIdx = 0;
|
|
|
|
if (NumEntries > 1)
|
|
{
|
|
const int32 LowerBoundIdx = LowerBound(PoseData.Entries.begin(), PoseData.Entries.end(), Time, [](const FPoseHistoryEntry& Entry, float Value) { return Value > Entry.AccumulatedSeconds; });
|
|
NextIdx = FMath::Clamp(LowerBoundIdx, 1, NumEntries - 1);
|
|
PrevIdx = NextIdx - 1;
|
|
}
|
|
|
|
const FPoseHistoryEntry& PrevEntry = PoseData.Entries[PrevIdx];
|
|
const FPoseHistoryEntry& NextEntry = PoseData.Entries[NextIdx];
|
|
|
|
return LerpEntries(Time, bExtrapolate, PrevEntry, NextEntry, CurveName, PoseData.CollectedCurves, OutCurveValue);
|
|
}
|
|
|
|
OutCurveValue = 0.0f;
|
|
return false;
|
|
}
|
|
|
|
const FTransformTrajectory& FPoseHistory::GetTrajectory() const
|
|
{
|
|
UE_MT_SCOPED_READ_ACCESS(PoseDataThreadSafeCounter);
|
|
return Trajectory;
|
|
}
|
|
|
|
float FPoseHistory::GetTrajectorySpeedMultiplier() const
|
|
{
|
|
UE_MT_SCOPED_READ_ACCESS(PoseDataThreadSafeCounter);
|
|
return TrajectorySpeedMultiplier;
|
|
}
|
|
|
|
bool FPoseHistory::IsEmpty() const
|
|
{
|
|
UE_MT_SCOPED_READ_ACCESS(PoseDataThreadSafeCounter);
|
|
return PoseData.Entries.IsEmpty();
|
|
}
|
|
|
|
const FBoneToTransformMap& FPoseHistory::GetBoneToTransformMap() const
|
|
{
|
|
UE_MT_SCOPED_READ_ACCESS(PoseDataThreadSafeCounter);
|
|
return PoseData.BoneToTransformMap;
|
|
}
|
|
|
|
const TConstArrayView<FName> FPoseHistory::GetCollectedCurves() const
|
|
{
|
|
UE_MT_SCOPED_READ_ACCESS(PoseDataThreadSafeCounter);
|
|
return MakeConstArrayView(PoseData.CollectedCurves);
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
void FPoseHistory::SetTrajectory(const FPoseSearchQueryTrajectory& InTrajectory, float InTrajectorySpeedMultiplier)
|
|
{
|
|
SetTrajectory(FTransformTrajectory(InTrajectory), InTrajectorySpeedMultiplier);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
int32 FPoseHistory::GetNumEntries() const
|
|
{
|
|
UE_MT_SCOPED_READ_ACCESS(PoseDataThreadSafeCounter);
|
|
return PoseData.Entries.Num();
|
|
}
|
|
|
|
const FPoseHistoryEntry& FPoseHistory::GetEntry(int32 EntryIndex) const
|
|
{
|
|
UE_MT_SCOPED_READ_ACCESS(PoseDataThreadSafeCounter);
|
|
return PoseData.Entries[EntryIndex];
|
|
}
|
|
|
|
void FPoseHistory::GenerateTrajectory(const UObject* AnimContext, float DeltaTime, const FPoseSearchTrajectoryData& TrajectoryData, const FPoseSearchTrajectoryData::FSampling& TrajectoryDataSampling)
|
|
{
|
|
// @todo: Synchronize the FPoseSearchQueryTrajectorySample::AccumulatedSeconds of the generated trajectory with the FPoseHistoryEntry::AccumulatedSeconds of the captured poses
|
|
FPoseSearchTrajectoryData::FDerived TrajectoryDataDerived;
|
|
if (TrajectoryData.UpdateData(DeltaTime, AnimContext, TrajectoryDataDerived, TrajectoryDataState))
|
|
{
|
|
UPoseSearchTrajectoryLibrary::InitTrajectorySamples(Trajectory, TrajectoryDataDerived.Position, TrajectoryDataDerived.Facing, TrajectoryDataSampling, DeltaTime);
|
|
UPoseSearchTrajectoryLibrary::UpdateHistory_TransformHistory(Trajectory, TrajectoryDataDerived.Position, TrajectoryDataDerived.Velocity, TrajectoryDataSampling, DeltaTime);
|
|
UPoseSearchTrajectoryLibrary::UpdatePrediction_SimulateCharacterMovement(Trajectory, TrajectoryData, TrajectoryDataDerived, TrajectoryDataSampling, DeltaTime);
|
|
|
|
// @todo: support TrajectorySpeedMultiplier
|
|
//TrajectorySpeedMultiplier = 1.f;
|
|
}
|
|
}
|
|
|
|
void FPoseHistory::GenerateTrajectory(const UObject* AnimContext, float DeltaTime)
|
|
{
|
|
GenerateTrajectory(AnimContext, DeltaTime, FPoseSearchTrajectoryData(), FPoseSearchTrajectoryData::FSampling());
|
|
}
|
|
|
|
void FPoseHistory::SetTrajectory(const FTransformTrajectory& InTrajectory, float InTrajectorySpeedMultiplier)
|
|
{
|
|
if (!InTrajectory.Samples.IsEmpty())
|
|
{
|
|
UE_MT_SCOPED_WRITE_ACCESS(PoseDataThreadSafeCounter);
|
|
|
|
// UE_MT_SCOPED_WRITE_ACCESS will assert in case of improper usage
|
|
Trajectory = InTrajectory;
|
|
|
|
TrajectorySpeedMultiplier = InTrajectorySpeedMultiplier;
|
|
|
|
if (!FMath::IsNearlyEqual(TrajectorySpeedMultiplier, 1.f))
|
|
{
|
|
const float TrajectorySpeedMultiplierInv = FMath::IsNearlyZero(TrajectorySpeedMultiplier) ? 1.f : 1.f / TrajectorySpeedMultiplier;
|
|
for (FTransformTrajectorySample& Sample : Trajectory.Samples)
|
|
{
|
|
Sample.TimeInSeconds *= TrajectorySpeedMultiplierInv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPoseHistory::EvaluateComponentSpace_AnyThread(float DeltaTime, IComponentSpacePoseProvider& ComponentSpacePoseProvider, bool bStoreScales,
|
|
float RootBoneRecoveryTime, float RootBoneTranslationRecoveryRatio, float RootBoneRotationRecoveryRatio,
|
|
bool bNeedsReset, bool bCacheBones, const TArray<FBoneIndexType>& RequiredBones,
|
|
const FBlendedCurve& Curves, const TConstArrayView<FName>& CollectedCurves)
|
|
{
|
|
UE_MT_SCOPED_WRITE_ACCESS(PoseDataThreadSafeCounter);
|
|
|
|
check(MaxNumPoses >= 2);
|
|
|
|
const USkeleton* Skeleton = ComponentSpacePoseProvider.GetSkeletonAsset();
|
|
|
|
if (bCacheBones)
|
|
{
|
|
const uint32 OldBoneToTransformMapTypeHash = PoseData.BoneToTransformMapTypeHash;
|
|
|
|
PoseData.BoneToTransformMap.Reset();
|
|
PoseData.CollectedCurves = CollectedCurves;
|
|
if (!RequiredBones.IsEmpty())
|
|
{
|
|
// making sure we always collect the root bone transform (by construction BoneToTransformMap[0] = 0)
|
|
const FComponentSpaceTransformIndex ComponentSpaceTransformRootBoneIndex = 0;
|
|
PoseData.BoneToTransformMap.Add(RootBoneIndexType) = ComponentSpaceTransformRootBoneIndex;
|
|
|
|
for (int32 i = 0; i < RequiredBones.Num(); ++i)
|
|
{
|
|
// adding only unique RequiredBones to avoid oversizing Entries::ComponentSpaceTransforms
|
|
if (!PoseData.BoneToTransformMap.Find(RequiredBones[i]))
|
|
{
|
|
const FComponentSpaceTransformIndex ComponentSpaceTransformIndex = PoseData.BoneToTransformMap.Num();
|
|
PoseData.BoneToTransformMap.Add(RequiredBones[i]) = ComponentSpaceTransformIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
PoseData.BoneToTransformMapTypeHash = GetTypeHash(PoseData.BoneToTransformMap);
|
|
bNeedsReset |= (OldBoneToTransformMapTypeHash != PoseData.BoneToTransformMapTypeHash);
|
|
}
|
|
|
|
if (PoseData.LastUpdateSkeleton != Skeleton)
|
|
{
|
|
bNeedsReset = true;
|
|
PoseData.LastUpdateSkeleton = Skeleton;
|
|
}
|
|
|
|
if (bNeedsReset)
|
|
{
|
|
PoseData.Entries.Reset();
|
|
PoseData.Entries.Reserve(MaxNumPoses);
|
|
}
|
|
|
|
FPoseHistoryEntry FutureEntryTemp;
|
|
if (!PoseData.Entries.IsEmpty() && PoseData.Entries.Last().AccumulatedSeconds > 0.f)
|
|
{
|
|
// removing the "future" root bone Entry
|
|
FutureEntryTemp = MoveTemp(PoseData.Entries.Last());
|
|
PoseData.Entries.Pop();
|
|
}
|
|
|
|
// Age our elapsed times
|
|
for (FPoseHistoryEntry& Entry : PoseData.Entries)
|
|
{
|
|
Entry.AccumulatedSeconds -= DeltaTime;
|
|
}
|
|
|
|
if (PoseData.Entries.Num() != MaxNumPoses)
|
|
{
|
|
// Consume every pose until the queue is full
|
|
PoseData.Entries.Emplace();
|
|
}
|
|
else if (SamplingInterval <= 0.f || PoseData.Entries[PoseData.Entries.Num() - 2].AccumulatedSeconds <= -SamplingInterval)
|
|
{
|
|
FPoseHistoryEntry EntryTemp = MoveTemp(PoseData.Entries.First());
|
|
PoseData.Entries.PopFront();
|
|
PoseData.Entries.Emplace(MoveTemp(EntryTemp));
|
|
}
|
|
|
|
// Regardless of the retention policy, we always update the most recent Entry
|
|
FPoseHistoryEntry& MostRecentEntry = PoseData.Entries.Last();
|
|
MostRecentEntry.Update(0.f, ComponentSpacePoseProvider, PoseData.BoneToTransformMap, bStoreScales, Curves, PoseData.CollectedCurves);
|
|
|
|
if (RootBoneRecoveryTime > 0.f && !Trajectory.Samples.IsEmpty())
|
|
{
|
|
// adding the updated "future" root bone Entry
|
|
const FTransform& RefRootBone = Skeleton->GetReferenceSkeleton().GetRefBonePose()[RootBoneIndexType];
|
|
const FQuat RootBoneRotationAtRecoveryTime = FMath::Lerp(FQuat(MostRecentEntry.ComponentSpaceRotations[RootBoneIndexType]), RefRootBone.GetRotation(), RootBoneRotationRecoveryRatio);
|
|
|
|
FVector RootBoneDeltaTranslationAtRecoveryTime = FVector::ZeroVector;
|
|
if (RootBoneTranslationRecoveryRatio > 0.f)
|
|
{
|
|
const FTransform WorldRootAtCurrentTime = Trajectory.GetSampleAtTime(0.f).GetTransform();
|
|
const FTransform WorldRootBoneAtCurrentTime = MostRecentEntry.GetComponentSpaceTransform(RootBoneIndexType) * WorldRootAtCurrentTime;
|
|
const FVector WorldRootBoneDeltaTranslationAtCurrentTime = (WorldRootBoneAtCurrentTime.GetTranslation() - WorldRootAtCurrentTime.GetTranslation()) * RootBoneTranslationRecoveryRatio;
|
|
const FTransform WorldRootAtRecoveryTime = Trajectory.GetSampleAtTime(RootBoneRecoveryTime).GetTransform();
|
|
RootBoneDeltaTranslationAtRecoveryTime = WorldRootAtRecoveryTime.InverseTransformVector(WorldRootBoneDeltaTranslationAtCurrentTime);
|
|
}
|
|
|
|
const FTransform RootBoneTransformAtRecoveryTime(RootBoneRotationAtRecoveryTime, RootBoneDeltaTranslationAtRecoveryTime, RefRootBone.GetScale3D());
|
|
FutureEntryTemp.SetNum(1, bStoreScales);
|
|
FutureEntryTemp.SetComponentSpaceTransform(RootBoneIndexType, RootBoneTransformAtRecoveryTime);
|
|
FutureEntryTemp.AccumulatedSeconds = RootBoneRecoveryTime;
|
|
PoseData.Entries.Emplace(MoveTemp(FutureEntryTemp));
|
|
}
|
|
}
|
|
|
|
void FPoseHistory::EvaluateComponentSpace_AnyThread(float DeltaTime, FCSPose<FCompactPose>& ComponentSpacePose, bool bStoreScales,
|
|
float RootBoneRecoveryTime, float RootBoneTranslationRecoveryRatio, float RootBoneRotationRecoveryRatio,
|
|
bool bNeedsReset, bool bCacheBones, const TArray<FBoneIndexType>& RequiredBones,
|
|
const FBlendedCurve& Curves, const TConstArrayView<FName>& CollectedCurves)
|
|
{
|
|
FComponentSpacePoseProvider ComponentSpacePoseProvider(ComponentSpacePose);
|
|
EvaluateComponentSpace_AnyThread(DeltaTime, ComponentSpacePoseProvider, bStoreScales, RootBoneRecoveryTime, RootBoneTranslationRecoveryRatio,
|
|
RootBoneRotationRecoveryRatio, bNeedsReset, bCacheBones, RequiredBones, Curves, CollectedCurves);
|
|
}
|
|
|
|
#if ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUG
|
|
void FPoseHistory::DebugDraw(FAnimInstanceProxy& AnimInstanceProxy, FColor Color) const
|
|
{
|
|
UE_MT_SCOPED_READ_ACCESS(PoseDataThreadSafeCounter);
|
|
|
|
if (GVarAnimPoseHistoryDebugDrawTrajectory)
|
|
{
|
|
const float DebugThickness = GVarAnimPoseHistoryDebugDrawTrajectoryThickness;
|
|
const int MaxHistorySamples = GVarAnimPoseHistoryDebugDrawTrajectoryMaxNumOfHistorySamples;
|
|
const int MaxPredictionSamples = GVarAnimPoseHistoryDebugDrawTrajectoryMaxNumOfPredictionSamples;
|
|
UTransformTrajectoryBlueprintLibrary::DebugDrawTrajectory(Trajectory, AnimInstanceProxy, DebugThickness, 0, MaxHistorySamples, MaxPredictionSamples);
|
|
}
|
|
|
|
if (Color.A > 0 && GVarAnimPoseHistoryDebugDrawPose)
|
|
{
|
|
const bool bValidTrajectory = !Trajectory.Samples.IsEmpty();
|
|
TArray<FTransform, TInlineAllocator<128>> PrevGlobalTransforms;
|
|
|
|
for (int32 EntryIndex = 0; EntryIndex < PoseData.Entries.Num(); ++EntryIndex)
|
|
{
|
|
const FPoseHistoryEntry& Entry = PoseData.Entries[EntryIndex];
|
|
|
|
const int32 PrevGlobalTransformsNum = PrevGlobalTransforms.Num();
|
|
const int32 Max = FMath::Max(PrevGlobalTransformsNum, Entry.Num());
|
|
|
|
PrevGlobalTransforms.SetNum(Max, EAllowShrinking::No);
|
|
|
|
for (int32 i = 0; i < Entry.Num(); ++i)
|
|
{
|
|
const FTransform RootTransform = bValidTrajectory ? Trajectory.GetSampleAtTime(Entry.AccumulatedSeconds).GetTransform() : AnimInstanceProxy.GetComponentTransform();
|
|
const FTransform GlobalTransforms = Entry.GetComponentSpaceTransform(i) * RootTransform;
|
|
|
|
if (i < PrevGlobalTransformsNum)
|
|
{
|
|
AnimInstanceProxy.AnimDrawDebugLine(PrevGlobalTransforms[i].GetTranslation(), GlobalTransforms.GetTranslation(), Color, false, 0.f, ESceneDepthPriorityGroup::SDPG_Foreground);
|
|
}
|
|
|
|
PrevGlobalTransforms[i] = GlobalTransforms;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUG
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FGenerateTrajectoryPoseHistory
|
|
void FGenerateTrajectoryPoseHistory::GenerateTrajectory(const UObject* AnimContext, float DeltaTime)
|
|
{
|
|
if (bGenerateTrajectory && !bIsTrajectoryGeneratedBeforePreUpdate)
|
|
{
|
|
FPoseHistory::GenerateTrajectory(AnimContext, DeltaTime, TrajectoryData, TrajectoryDataSampling);
|
|
bIsTrajectoryGeneratedBeforePreUpdate = true;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FMemStackPoseHistory
|
|
void FMemStackPoseHistory::Init(const IPoseHistory* InPoseHistory)
|
|
{
|
|
check(InPoseHistory);
|
|
PoseHistory = InPoseHistory;
|
|
}
|
|
|
|
void FMemStackPoseHistory::SetTrajectory(const FTransformTrajectory& InTrajectory, float InTrajectorySpeedMultiplier)
|
|
{
|
|
check(PoseHistory);
|
|
// FMemStackPoseHistory should never change the trajectory!
|
|
checkNoEntry();
|
|
}
|
|
|
|
void FMemStackPoseHistory::GenerateTrajectory(const UObject* AnimContext, float DeltaTime)
|
|
{
|
|
check(PoseHistory);
|
|
// FMemStackPoseHistory should never change the trajectory!
|
|
checkNoEntry();
|
|
}
|
|
|
|
bool FMemStackPoseHistory::GetTransformAtTime(float Time, FTransform& OutBoneTransform, const USkeleton* BoneIndexSkeleton, FBoneIndexType BoneIndexType, FBoneIndexType ReferenceBoneIndexType, bool bExtrapolate) const
|
|
{
|
|
check(PoseHistory);
|
|
|
|
const int32 Num = FutureEntries.Num();
|
|
if (Time > 0.f && Num > 0)
|
|
{
|
|
if (BoneIndexType == ReferenceBoneIndexType)
|
|
{
|
|
OutBoneTransform = FTransform::Identity;
|
|
return true;
|
|
}
|
|
|
|
if (ReferenceBoneIndexType == WorldSpaceIndexType)
|
|
{
|
|
const FTransformTrajectory& Trajectory = GetTrajectory();
|
|
|
|
if (BoneIndexType == ComponentSpaceIndexType)
|
|
{
|
|
OutBoneTransform = Trajectory.GetSampleAtTime(Time, bExtrapolate).GetTransform();
|
|
return true;
|
|
}
|
|
|
|
// getting BoneIndexType in ComponentSpaceIndexType and then multiplying it with the component to world transform from the trajectory
|
|
const bool bSuccess = GetTransformAtTime(Time, OutBoneTransform, BoneIndexSkeleton, BoneIndexType, ComponentSpaceIndexType, bExtrapolate);
|
|
OutBoneTransform *= Trajectory.GetSampleAtTime(Time, bExtrapolate).GetTransform();
|
|
return bSuccess;
|
|
}
|
|
|
|
if (BoneIndexType == WorldSpaceIndexType || BoneIndexType == ComponentSpaceIndexType)
|
|
{
|
|
const bool bSuccess = GetTransformAtTime(Time, OutBoneTransform, BoneIndexSkeleton, ReferenceBoneIndexType, BoneIndexType, bExtrapolate);
|
|
OutBoneTransform = OutBoneTransform.Inverse();
|
|
return bSuccess;
|
|
}
|
|
|
|
check(BoneIndexType != ComponentSpaceIndexType && BoneIndexType != WorldSpaceIndexType && ReferenceBoneIndexType != WorldSpaceIndexType);
|
|
|
|
const int32 LowerBoundIdx = Algo::LowerBound(FutureEntries, Time, [](const FPoseHistoryEntry& Entry, float Value) { return Value > Entry.AccumulatedSeconds; });
|
|
const int32 NextIdx = FMath::Min(LowerBoundIdx, Num - 1);
|
|
const FPoseHistoryEntry& NextEntry = FutureEntries[NextIdx];
|
|
const FPoseHistoryEntry& PrevEntry = NextIdx > 0 ? FutureEntries[NextIdx - 1] : PoseHistory->GetNumEntries() > 0 ? PoseHistory->GetEntry(PoseHistory->GetNumEntries() - 1) : NextEntry;
|
|
|
|
return LerpEntries(Time, bExtrapolate, PrevEntry, NextEntry, BoneIndexSkeleton, nullptr, GetBoneToTransformMap(), BoneIndexType, ReferenceBoneIndexType, OutBoneTransform);
|
|
}
|
|
|
|
return PoseHistory->GetTransformAtTime(Time, OutBoneTransform, BoneIndexSkeleton, BoneIndexType, ReferenceBoneIndexType, bExtrapolate);
|
|
}
|
|
|
|
bool FMemStackPoseHistory::GetCurveValueAtTime(float Time, const FName& CurveName, float& OutCurveValue, bool bExtrapolate /*= false*/) const
|
|
{
|
|
check(PoseHistory);
|
|
|
|
const int32 Num = FutureEntries.Num();
|
|
if (Time > 0.f && Num > 0)
|
|
{
|
|
const int32 LowerBoundIdx = Algo::LowerBound(FutureEntries, Time, [](const FPoseHistoryEntry& Entry, float Value) { return Value > Entry.AccumulatedSeconds; });
|
|
const int32 NextIdx = FMath::Min(LowerBoundIdx, Num - 1);
|
|
const FPoseHistoryEntry& NextEntry = FutureEntries[NextIdx];
|
|
const FPoseHistoryEntry& PrevEntry = NextIdx > 0 ? FutureEntries[NextIdx - 1] : PoseHistory->GetNumEntries() > 0 ? PoseHistory->GetEntry(PoseHistory->GetNumEntries() - 1) : NextEntry;
|
|
|
|
return LerpEntries(Time, bExtrapolate, PrevEntry, NextEntry, CurveName, GetCollectedCurves(), OutCurveValue);
|
|
}
|
|
|
|
return PoseHistory->GetCurveValueAtTime(Time, CurveName, OutCurveValue, bExtrapolate);
|
|
}
|
|
|
|
void FMemStackPoseHistory::AddFutureRootBone(float Time, const FTransform& FutureRootBoneTransform, bool bStoreScales)
|
|
{
|
|
// we don't allow to add "past" or "present" poses to FutureEntries
|
|
check(Time > 0.f);
|
|
|
|
const int32 LowerBoundIdx = Algo::LowerBound(FutureEntries, Time, [](const FPoseHistoryEntry& Entry, float Value) { return Value > Entry.AccumulatedSeconds; });
|
|
FPoseHistoryEntry& FutureEntry = FutureEntries.InsertDefaulted_GetRef(LowerBoundIdx);
|
|
FutureEntry.SetNum(1, bStoreScales);
|
|
FutureEntry.SetComponentSpaceTransform(RootBoneIndexType, FutureRootBoneTransform);
|
|
FutureEntry.AccumulatedSeconds = Time;
|
|
}
|
|
|
|
void FMemStackPoseHistory::AddFuturePose(float Time, IComponentSpacePoseProvider& ComponentSpacePoseProvider, const FBlendedCurve& Curves)
|
|
{
|
|
// we don't allow to add "past" or "present" poses to FutureEntries
|
|
check(Time > 0.f);
|
|
check(PoseHistory);
|
|
const int32 LowerBoundIdx = Algo::LowerBound(FutureEntries, Time, [](const FPoseHistoryEntry& Entry, float Value) { return Value > Entry.AccumulatedSeconds; });
|
|
FutureEntries.InsertDefaulted_GetRef(LowerBoundIdx).Update(Time, ComponentSpacePoseProvider, GetBoneToTransformMap(), true, Curves, MakeConstArrayView(GetCollectedCurves()));
|
|
}
|
|
|
|
void FMemStackPoseHistory::ExtractAndAddFuturePoses(const UAnimationAsset* AnimationAsset, float AnimationTime, float FiniteDeltaTime, const FVector& BlendParame1ters, float IntervalTime, const USkeleton* OverrideSkeleton, bool bUseRefPoseRootBone)
|
|
{
|
|
FMemMark Mark(FMemStack::Get());
|
|
|
|
check(FiniteDeltaTime >= 0.f);
|
|
if (AnimationTime < FiniteDeltaTime)
|
|
{
|
|
UE_LOG(LogPoseSearch, Error, TEXT("FMemStackPoseHistory::ExtractAndAddFuturePose - provided AnimationTime (%f) is too small. Clamping it to minimum value of %f"), AnimationTime, FiniteDeltaTime);
|
|
AnimationTime = FiniteDeltaTime;
|
|
}
|
|
|
|
if (IntervalTime < FiniteDeltaTime)
|
|
{
|
|
UE_LOG(LogPoseSearch, Error, TEXT("FMemStackPoseHistory::ExtractAndAddFuturePose - provided IntervalTime (%f) is too small. Clamping it to minimum value of %f"), IntervalTime, FiniteDeltaTime);
|
|
AnimationTime = FiniteDeltaTime;
|
|
}
|
|
|
|
const USkeleton* Skeleton = OverrideSkeleton ? OverrideSkeleton : AnimationAsset->GetSkeleton();
|
|
TArray<uint16> BoneIndices;
|
|
const FBoneToTransformMap& BoneToTransformMap = GetBoneToTransformMap();
|
|
if (BoneToTransformMap.IsEmpty())
|
|
{
|
|
const int32 NumBones = Skeleton->GetReferenceSkeleton().GetNum();
|
|
BoneIndices.SetNum(NumBones);
|
|
for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
|
|
{
|
|
BoneIndices[BoneIndex] = BoneIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const FBoneToTransformPair& BoneToTransformPair : BoneToTransformMap)
|
|
{
|
|
BoneIndices.Add(BoneToTransformPair.Key);
|
|
}
|
|
|
|
BoneIndices.Sort();
|
|
FAnimationRuntime::EnsureParentsPresent(BoneIndices, Skeleton->GetReferenceSkeleton());
|
|
}
|
|
|
|
FBoneContainer BoneContainer;
|
|
BoneContainer.InitializeTo(BoneIndices, UE::Anim::FCurveFilterSettings(UE::Anim::ECurveFilterMode::DisallowAll), *Skeleton);
|
|
|
|
// extracting 2 poses to be able to calculate velocities
|
|
FCSPose<FCompactPose> ComponentSpacePose;
|
|
FCompactPose Pose;
|
|
FBlendedCurve Curves;
|
|
Pose.SetBoneContainer(&BoneContainer);
|
|
|
|
// extracting 2 poses to be able to calculate velocities
|
|
const FAnimationAssetSampler Sampler(AnimationAsset, FTransform::Identity, FVector::ZeroVector, FAnimationAssetSampler::DefaultRootTransformSamplingRate, false, false);
|
|
|
|
const int32 NumOfPoseExtractions = FMath::IsNearlyZero(FiniteDeltaTime) ? 1 : 2;
|
|
for (int32 i = 0; i < NumOfPoseExtractions; ++i)
|
|
{
|
|
const float FuturePoseExtractionTime = AnimationTime + (i - 1) * FiniteDeltaTime;
|
|
const float FuturePoseAnimationTime = IntervalTime + (i - 1) * FiniteDeltaTime;
|
|
|
|
Sampler.ExtractPose(FuturePoseExtractionTime, Pose, Curves);
|
|
|
|
if (bUseRefPoseRootBone)
|
|
{
|
|
Pose[FCompactPoseBoneIndex(RootBoneIndexType)] = Skeleton->GetReferenceSkeleton().GetRefBonePose()[RootBoneIndexType];
|
|
}
|
|
|
|
ComponentSpacePose.InitPose(Pose);
|
|
FComponentSpacePoseProvider ComponentSpacePoseProvider(ComponentSpacePose);
|
|
AddFuturePose(FuturePoseAnimationTime, ComponentSpacePoseProvider);
|
|
}
|
|
}
|
|
|
|
void FMemStackPoseHistory::AddFuturePose(float Time, FCSPose<FCompactPose>& ComponentSpacePose, const FBlendedCurve& Curves)
|
|
{
|
|
FComponentSpacePoseProvider ComponentSpacePoseProvider(ComponentSpacePose);
|
|
AddFuturePose(Time, ComponentSpacePoseProvider, Curves);
|
|
}
|
|
|
|
int32 FMemStackPoseHistory::GetNumEntries() const
|
|
{
|
|
check(PoseHistory);
|
|
return PoseHistory->GetNumEntries() + FutureEntries.Num();
|
|
}
|
|
|
|
const FPoseHistoryEntry& FMemStackPoseHistory::GetEntry(int32 EntryIndex) const
|
|
{
|
|
check(PoseHistory);
|
|
|
|
const int32 PoseHistoryNumEntries = PoseHistory->GetNumEntries();
|
|
if (EntryIndex < PoseHistoryNumEntries)
|
|
{
|
|
return PoseHistory->GetEntry(EntryIndex);
|
|
}
|
|
return FutureEntries[EntryIndex - PoseHistoryNumEntries];
|
|
}
|
|
|
|
#if ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUG
|
|
void FMemStackPoseHistory::DebugDraw(FAnimInstanceProxy& AnimInstanceProxy, FColor Color) const
|
|
{
|
|
check(PoseHistory);
|
|
|
|
if (Color.A > 0 && !FutureEntries.IsEmpty() && GVarAnimPoseHistoryDebugDrawPose)
|
|
{
|
|
const FTransformTrajectory& Trajectory = GetTrajectory();
|
|
const bool bValidTrajectory = !Trajectory.Samples.IsEmpty();
|
|
TArray<FTransform, TInlineAllocator<128>> PrevGlobalTransforms;
|
|
|
|
int32 EntriesNum = FutureEntries.Num();
|
|
if (PoseHistory->GetNumEntries() > 0)
|
|
{
|
|
// connecting the future entries with the past entries
|
|
++EntriesNum;
|
|
}
|
|
|
|
for (int32 EntryIndex = 0; EntryIndex < EntriesNum; ++EntryIndex)
|
|
{
|
|
const FPoseHistoryEntry& Entry = (EntryIndex == FutureEntries.Num()) ? PoseHistory->GetEntry(PoseHistory->GetNumEntries() - 1) : FutureEntries[EntryIndex];
|
|
|
|
const int32 PrevGlobalTransformsNum = PrevGlobalTransforms.Num();
|
|
const int32 Max = FMath::Max(PrevGlobalTransformsNum, Entry.Num());
|
|
|
|
PrevGlobalTransforms.SetNum(Max, EAllowShrinking::No);
|
|
|
|
for (int32 i = 0; i < Entry.Num(); ++i)
|
|
{
|
|
const FTransform RootTransform = bValidTrajectory ? Trajectory.GetSampleAtTime(Entry.AccumulatedSeconds).GetTransform() : AnimInstanceProxy.GetComponentTransform();
|
|
const FTransform GlobalTransforms = Entry.GetComponentSpaceTransform(i) * RootTransform;
|
|
|
|
if (i < PrevGlobalTransformsNum)
|
|
{
|
|
AnimInstanceProxy.AnimDrawDebugLine(PrevGlobalTransforms[i].GetTranslation(), GlobalTransforms.GetTranslation(), Color, false, 0.f, ESceneDepthPriorityGroup::SDPG_Foreground);
|
|
}
|
|
|
|
PrevGlobalTransforms[i] = GlobalTransforms;
|
|
}
|
|
}
|
|
|
|
// no need to DebugDraw PoseHistory since it'll be drawn anyways by the history collectors
|
|
//PoseHistory->DebugDraw(AnimInstanceProxy, Color);
|
|
}
|
|
}
|
|
#endif // ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUG
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FPoseIndicesHistory
|
|
void FPoseIndicesHistory::Update(const FSearchResult& SearchResult, float DeltaTime, float MaxTime)
|
|
{
|
|
if (MaxTime > 0.f)
|
|
{
|
|
for (auto It = IndexToTime.CreateIterator(); It; ++It)
|
|
{
|
|
It.Value() += DeltaTime;
|
|
if (It.Value() > MaxTime)
|
|
{
|
|
It.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
if (SearchResult.IsValid())
|
|
{
|
|
FHistoricalPoseIndex HistoricalPoseIndex;
|
|
HistoricalPoseIndex.PoseIndex = SearchResult.PoseIdx;
|
|
HistoricalPoseIndex.DatabaseKey = FObjectKey(SearchResult.Database.Get());
|
|
IndexToTime.Add(HistoricalPoseIndex, 0.f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IndexToTime.Reset();
|
|
}
|
|
}
|
|
|
|
} // namespace UE::PoseSearch
|