Files
UnrealEngineUWP/Engine/Source/Runtime/AnimGraphRuntime/Private/AnimNodes/AnimNode_PoseDriver.cpp
thomas sarkanen a897f0439a Skeleton compatibility improvements
Skeleton compatibility is now bi-directional. Specifying a compatible skeleton A -> B now implies B -> A.
Skeleton compatibility is now an editor-only concern. The runtime will attempt to do the 'best it can' via name -> name mappings. Only the editor will prevent assigning incompatible skeletons in (e.g.) asset pickers etc.
Skeleton compatibility checks in editor can now be disabled in the editor preferences (and each asset picker now has a checkbox option in its view settings that allows for quick access to this).

Moves FSkeletonRemapping to its own file (which is now private).
Skeleton remappings are now generated on demand on worker threads just before animation decompression and stored in a registry, guarded by FRWScopeLock for thread-safety.

Fixed some anim BP compiler edge cases where asset references on pins were not getting preloaded correctly, causing skeletons to be erroneously reported as missing.

Exposed the current asset registry filter in SAssetView so that menu extensions can access it (and use it to provide context)

#jira UE-166054
#jira UE-167355
#rb Jurre.deBaare,John.vanderBerg
#preflight 635902602e6690262afa86f9
#preflight 6372ad7a0c74adb48b472b38
#p4v-cherrypick 22878911
#preflight 6374b3c51d25fe8b931b8d7b

[CL 23152671 by thomas sarkanen in ue5-main branch]
2022-11-16 07:21:38 -05:00

499 lines
15 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimNodes/AnimNode_PoseDriver.h"
#include "AnimationRuntime.h"
#include "Serialization/CustomVersion.h"
#include "Animation/AnimInstanceProxy.h"
#include "RBF/RBFSolver.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_PoseDriver)
FAnimNode_PoseDriver::FAnimNode_PoseDriver()
: DriveSource(EPoseDriverSource::Rotation)
, DriveOutput(EPoseDriverOutput::DrivePoses)
, bOnlyDriveSelectedBones(false)
, LODThreshold(INDEX_NONE)
{
RBFParams.DistanceMethod = ERBFDistanceMethod::SwingAngle;
#if WITH_EDITORONLY_DATA
SoloTargetIndex = INDEX_NONE;
bSoloDrivenOnly = false;
RadialScaling_DEPRECATED = 0.25f;
Type_DEPRECATED = EPoseDriverType::SwingOnly;
TwistAxis_DEPRECATED = BA_X;
#endif
}
void FAnimNode_PoseDriver::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
FAnimNode_PoseHandler::Initialize_AnyThread(Context);
SourcePose.Initialize(Context);
}
void FAnimNode_PoseDriver::RebuildPoseList(const FBoneContainer& InBoneContainer, const UPoseAsset* InPoseAsset)
{
// Cache UIDs for driving curves
PoseExtractContext.PoseCurves.Reset();
const USkeleton* Skeleton = InPoseAsset->GetSkeleton();
if (Skeleton)
{
const TArray<FSmartName>& PoseNames = InPoseAsset->GetPoseNames();
for (FPoseDriverTarget& PoseTarget : PoseTargets)
{
if (DriveOutput == EPoseDriverOutput::DriveCurves)
{
PoseTarget.DrivenUID = Skeleton->GetUIDByName(USkeleton::AnimCurveMappingName, PoseTarget.DrivenName);
}
else
{
PoseTarget.DrivenUID = SmartName::MaxUID;
}
const int32 PoseIndex = InPoseAsset->GetPoseIndexByName(PoseTarget.DrivenName);
if (PoseIndex != INDEX_NONE)
{
TArray<uint16> const& LUTIndex = InBoneContainer.GetUIDToArrayLookupTable();
if (LUTIndex.IsValidIndex(PoseNames[PoseIndex].UID) && LUTIndex[PoseNames[PoseIndex].UID] != MAX_uint16)
{
// we keep pose index as that is the fastest way to search when extracting pose asset
PoseTarget.PoseCurveIndex = PoseExtractContext.PoseCurves.Add(FPoseCurve(PoseIndex, PoseNames[PoseIndex].UID, 0.f));
}
else
{
PoseTarget.PoseCurveIndex = INDEX_NONE;
}
}
else
{
PoseTarget.PoseCurveIndex = INDEX_NONE;
}
}
}
}
void FAnimNode_PoseDriver::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(CacheBones_AnyThread)
FAnimNode_PoseHandler::CacheBones_AnyThread(Context);
// Init pose input
SourcePose.CacheBones(Context);
const FBoneContainer& BoneContainer = Context.AnimInstanceProxy->GetRequiredBones();
// Init bone refs
for (FBoneReference& SourceBoneRef : SourceBones)
{
SourceBoneRef.Initialize(BoneContainer);
}
for (FBoneReference& OnlyDriveBoneRef : OnlyDriveBones)
{
OnlyDriveBoneRef.Initialize(BoneContainer);
}
EvalSpaceBone.Initialize(BoneContainer);
// Don't want to modify SourceBones, set weight to zero (if weight array is allocated)
for (FBoneReference& SourceBoneRef : SourceBones)
{
const FCompactPoseBoneIndex SourceCompactIndex = SourceBoneRef.GetCompactPoseIndex(BoneContainer);
if (BoneBlendWeights.IsValidIndex(SourceCompactIndex.GetInt()))
{
BoneBlendWeights[SourceCompactIndex.GetInt()] = 0.f;
}
}
// If we are filtering for only specific bones, set blend weight to zero for unwanted bones, and remember which bones to filter
BonesToFilter.Reset();
if (bOnlyDriveSelectedBones && CurrentPoseAsset.IsValid())
{
// Super call above should init BoneBlendWeights to compact pose size if CurrentPoseAsset is valid
check(BoneBlendWeights.Num() == BoneContainer.GetBoneIndicesArray().Num());
const TArray<FName> TrackNames = CurrentPoseAsset.Get()->GetTrackNames();
for (const auto& TrackName : TrackNames)
{
// See if bone is in select list
if (!IsBoneDriven(TrackName))
{
int32 MeshBoneIndex = BoneContainer.GetPoseBoneIndexForBoneName(TrackName);
FCompactPoseBoneIndex CompactBoneIndex = BoneContainer.MakeCompactPoseIndex(FMeshPoseBoneIndex(MeshBoneIndex));
if (CompactBoneIndex != INDEX_NONE)
{
BoneBlendWeights[CompactBoneIndex.GetInt()] = 0.f; // Set blend weight for non-additive
BonesToFilter.Add(CompactBoneIndex); // Remember bones to filter out for additive
}
}
}
}
PoseExtractContext.BonesRequired.SetNumZeroed(BoneBlendWeights.Num());
for (int32 BoneIndex = 0; BoneIndex < BoneBlendWeights.Num(); BoneIndex++)
{
PoseExtractContext.BonesRequired[BoneIndex] = BoneBlendWeights[BoneIndex] > SMALL_NUMBER;
}
}
void FAnimNode_PoseDriver::UpdateAssetPlayer(const FAnimationUpdateContext& Context)
{
FAnimNode_PoseHandler::UpdateAssetPlayer(Context);
SourcePose.Update(Context);
}
void FAnimNode_PoseDriver::GatherDebugData(FNodeDebugData& DebugData)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
FAnimNode_PoseHandler::GatherDebugData(DebugData);
SourcePose.GatherDebugData(DebugData.BranchFlow(1.f));
}
float FAnimNode_PoseDriver::GetRadiusForTarget(const FRBFTarget& Target) const
{
return FRBFSolver::GetRadiusForTarget(Target, RBFParams);
}
bool FAnimNode_PoseDriver::IsBoneDriven(FName BoneName) const
{
// If not filtering, drive all the bones
if (!bOnlyDriveSelectedBones)
{
return true;
}
bool bIsDriven = false;
for (const FBoneReference& BoneRef : OnlyDriveBones)
{
if (BoneRef.BoneName == BoneName)
{
bIsDriven = true;
break;
}
}
return bIsDriven;
}
void FAnimNode_PoseDriver::GetRBFTargets(TArray<FRBFTarget>& OutTargets, const FBoneContainer* BoneContainer) const
{
OutTargets.Reset();
OutTargets.AddZeroed(PoseTargets.Num());
// Create entry for each target
for (int32 TargetIdx = 0; TargetIdx < PoseTargets.Num(); TargetIdx++)
{
FRBFTarget& RBFTarget = OutTargets[TargetIdx];
const FPoseDriverTarget& PoseTarget = PoseTargets[TargetIdx];
// We want to make sure we always have the right number of Values in our RBFTarget.
// If bone entries are missing, we fill with zeroes
for (int32 SourceIdx = 0; SourceIdx < SourceBones.Num(); SourceIdx++)
{
if (PoseTarget.BoneTransforms.IsValidIndex(SourceIdx))
{
const FPoseDriverTransform& BoneTransform = PoseTarget.BoneTransforms[SourceIdx];
// Get Ref Transform
FTransform RefBoneTransform = FTransform::Identity;
if (bEvalFromRefPose && BoneContainer)
{
const FCompactPoseBoneIndex CompactPoseIndex = SourceBones[SourceIdx].CachedCompactPoseIndex;
if (CompactPoseIndex < BoneContainer->GetCompactPoseNumBones())
{
RefBoneTransform = BoneContainer->GetRefPoseTransform(CompactPoseIndex);
}
}
// Target Translation
if (DriveSource == EPoseDriverSource::Translation)
{
// Make translation relative to its Ref
if (bEvalFromRefPose)
{
RBFTarget.AddFromVector(RefBoneTransform.Inverse().TransformPosition(BoneTransform.TargetTranslation));
}
else
{
RBFTarget.AddFromVector(BoneTransform.TargetTranslation);
}
}
// Target Rotation
else
{
// Make rotation relative to its Ref
if (bEvalFromRefPose)
{
const FQuat TargetRotation = BoneTransform.TargetRotation.Quaternion();
RBFTarget.AddFromRotator(RefBoneTransform.Inverse().TransformRotation(TargetRotation).Rotator());
}
else
{
RBFTarget.AddFromRotator(BoneTransform.TargetRotation);
}
}
}
else
{
RBFTarget.AddFromVector(FVector::ZeroVector);
}
}
RBFTarget.ScaleFactor = PoseTarget.TargetScale;
RBFTarget.bApplyCustomCurve = PoseTarget.bApplyCustomCurve;
RBFTarget.CustomCurve = PoseTarget.CustomCurve;
RBFTarget.DistanceMethod = PoseTarget.DistanceMethod;
RBFTarget.FunctionType = PoseTarget.FunctionType;
}
}
void FAnimNode_PoseDriver::Evaluate_AnyThread(FPoseContext& Output)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Evaluate_AnyThread)
QUICK_SCOPE_CYCLE_COUNTER(STAT_PoseDriver_Eval);
if (!IsLODEnabled(Output.AnimInstanceProxy))
{
SourcePose.Evaluate(Output);
return;
}
FPoseContext SourceData(Output);
SourcePose.Evaluate(SourceData);
// Udpate DrivenIDs if needed
if (bCachedDrivenIDsAreDirty)
{
if (CurrentPoseAsset.IsValid())
{
RebuildPoseList(Output.AnimInstanceProxy->GetRequiredBones(), CurrentPoseAsset.Get());
}
}
// Get the index of the source bone
const FBoneContainer& BoneContainer = SourceData.Pose.GetBoneContainer();
RBFInput.Values.Reset();
SourceBoneTMs.Reset();
bool bFoundAnyBone = false;
for (const FBoneReference& SourceBoneRef : SourceBones)
{
FTransform SourceBoneTM = FTransform::Identity;
const FCompactPoseBoneIndex SourceCompactIndex = SourceBoneRef.GetCompactPoseIndex(BoneContainer);
if (SourceCompactIndex.GetInt() != INDEX_NONE)
{
// If evaluating in alternative bone space, have to build component space pose
if (EvalSpaceBone.IsValidToEvaluate(BoneContainer))
{
FCSPose<FCompactPose> CSPose;
CSPose.InitPose(SourceData.Pose);
const FCompactPoseBoneIndex EvalSpaceCompactIndex = EvalSpaceBone.GetCompactPoseIndex(BoneContainer);
FTransform EvalSpaceCompSpace = CSPose.GetComponentSpaceTransform(EvalSpaceCompactIndex);
FTransform SourceBoneCompSpace = CSPose.GetComponentSpaceTransform(SourceCompactIndex);
SourceBoneTM = SourceBoneCompSpace.GetRelativeTransform(EvalSpaceCompSpace);
}
// If just evaluating in local space, just grab from local space pose
else
{
// Relative to Ref Pose
if (bEvalFromRefPose && SourceCompactIndex.GetInt() < BoneContainer.GetCompactPoseNumBones())
{
SourceBoneTM = SourceData.Pose[SourceCompactIndex].GetRelativeTransform(BoneContainer.GetRefPoseTransform(SourceCompactIndex));
}
else
{
SourceBoneTM = SourceData.Pose[SourceCompactIndex];
}
}
bFoundAnyBone = true;
}
// Build RBFInput entry
if (DriveSource == EPoseDriverSource::Translation)
{
RBFInput.AddFromVector(SourceBoneTM.GetTranslation());
}
else
{
RBFInput.AddFromRotator(SourceBoneTM.Rotator());
}
// Record this so we can use it for drawing in edit mode
SourceBoneTMs.Add(SourceBoneTM);
}
// Do nothing if bone is no bones are found/all LOD-ed out
if (!bFoundAnyBone)
{
Output = SourceData;
return;
}
RBFParams.TargetDimensions = SourceBones.Num() * 3;
OutputWeights.Reset();
#if WITH_EDITORONLY_DATA
if (SoloTargetIndex != INDEX_NONE && SoloTargetIndex < PoseTargets.Num())
{
OutputWeights.Add(FRBFOutputWeight(SoloTargetIndex, 1.0f));
}
else
#endif
{
// Get target array as RBF types
GetRBFTargets(RBFTargets, &BoneContainer);
if (!SolverData.IsValid() || !FRBFSolver::IsSolverDataValid(*SolverData, RBFParams, RBFTargets))
{
SolverData = FRBFSolver::InitSolver(RBFParams, RBFTargets);
}
// Run RBF solver
FRBFSolver::Solve(*SolverData, RBFParams, RBFTargets, RBFInput, OutputWeights);
}
// Track if we have filled Output with valid pose
bool bHaveValidPose = false;
// Process active targets (if any)
if (OutputWeights.Num() > 0)
{
// If we want to drive poses, and PoseAsset is assigned and compatible
const UPoseAsset* CachedPoseAsset = CurrentPoseAsset.Get();
if (DriveOutput == EPoseDriverOutput::DrivePoses &&
CachedPoseAsset &&
CachedPoseAsset->GetSkeleton() != nullptr)
{
FPoseContext CurrentPose(Output);
// clear the value before setting it.
for (int32 PoseIndex = 0; PoseIndex < PoseExtractContext.PoseCurves.Num(); ++PoseIndex)
{
PoseExtractContext.PoseCurves[PoseIndex].Value = 0.f;
}
// Then fill in weight for any driven poses
for (const FRBFOutputWeight& Weight : OutputWeights)
{
const FPoseDriverTarget& PoseTarget = PoseTargets[Weight.TargetIndex];
const int32 PoseIndex = PoseTarget.PoseCurveIndex;
if (PoseIndex != INDEX_NONE)
{
PoseExtractContext.PoseCurves[PoseIndex].Value = Weight.TargetWeight;
}
}
FAnimationPoseData CurrentAnimationPoseData(CurrentPose);
if (CurrentPoseAsset.Get()->GetAnimationPose(CurrentAnimationPoseData, PoseExtractContext))
{
// blend by weight
if (CurrentPoseAsset->IsValidAdditive())
{
const FTransform AdditiveIdentity(FQuat::Identity, FVector::ZeroVector, FVector::ZeroVector);
// Don't want to modify SourceBones, set additive offset to zero (not identity transform, as need zero scale)
for (const FBoneReference& SourceBoneRef : SourceBones)
{
const FCompactPoseBoneIndex SourceCompactIndex = SourceBoneRef.GetCompactPoseIndex(BoneContainer);
CurrentPose.Pose[SourceCompactIndex] = AdditiveIdentity;
}
// If filtering for specific bones, filter out bones using BonesToFilter array
if (bOnlyDriveSelectedBones)
{
for (FCompactPoseBoneIndex BoneIndex : BonesToFilter)
{
CurrentPose.Pose[BoneIndex] = AdditiveIdentity;
}
}
Output = SourceData;
FAnimationPoseData BaseAnimationPoseData(Output);
const FAnimationPoseData AdditiveAnimationPoseData(CurrentPose);
FAnimationRuntime::AccumulateAdditivePose(BaseAnimationPoseData, AdditiveAnimationPoseData, 1.f, EAdditiveAnimationType::AAT_LocalSpaceBase);
}
else
{
FAnimationPoseData BlendedAnimationPoseData(Output);
const FAnimationPoseData SourceAnimationPoseData(SourceData);
FAnimationRuntime::BlendTwoPosesTogetherPerBone(SourceAnimationPoseData, CurrentAnimationPoseData, BoneBlendWeights, BlendedAnimationPoseData);
}
bHaveValidPose = true;
}
}
// Drive curves (morphs, materials etc)
else if (DriveOutput == EPoseDriverOutput::DriveCurves)
{
// Start by copying input
Output = SourceData;
// Then set curves based on target weights
for (const FRBFOutputWeight& Weight : OutputWeights)
{
FPoseDriverTarget& PoseTarget = PoseTargets[Weight.TargetIndex];
if (PoseTarget.DrivenUID != SmartName::MaxUID)
{
Output.Curve.Set(PoseTarget.DrivenUID, Weight.TargetWeight);
}
}
bHaveValidPose = true;
}
}
// No valid pose, just pass through
if (!bHaveValidPose)
{
Output = SourceData;
}
#if WITH_EDITORONLY_DATA
else if (!bSoloDrivenOnly && SoloTargetIndex != INDEX_NONE && SoloTargetIndex < PoseTargets.Num())
{
SourceBoneTMs.Reset();
const FPoseDriverTarget& PoseTarget = PoseTargets[SoloTargetIndex];
for (int32 SourceIdx = 0; SourceIdx < SourceBones.Num(); SourceIdx++)
{
const FBoneReference& SourceBoneRef = SourceBones[SourceIdx];
const FCompactPoseBoneIndex SourceCompactIndex = SourceBoneRef.GetCompactPoseIndex(BoneContainer);
if (PoseTarget.BoneTransforms.IsValidIndex(SourceIdx) && SourceCompactIndex.GetInt() != INDEX_NONE)
{
FTransform& TargetTransform = Output.Pose[SourceCompactIndex];
const FPoseDriverTransform& SourceTransform = PoseTarget.BoneTransforms[SourceIdx];
if (DriveSource == EPoseDriverSource::Translation)
{
TargetTransform.SetTranslation(SourceTransform.TargetTranslation);
}
else
{
TargetTransform.SetRotation(SourceTransform.TargetRotation.Quaternion());
}
SourceBoneTMs.Add(TargetTransform);
}
}
}
#endif
}