987 lines
35 KiB
C++
987 lines
35 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "MetaHumanRigLogicUnpackLibrary.h"
|
|
|
|
#include "AnimationGraph.h"
|
|
#include "AnimGraphNode_ControlRig.h"
|
|
#include "AnimGraphNode_Root.h"
|
|
#include "Animation/AnimSequence.h"
|
|
#include "Animation/PoseAsset.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "ControlRigBlueprintFactory.h"
|
|
#include "DNAAsset.h"
|
|
#include "DNACommon.h"
|
|
#include "DNAReader.h"
|
|
#include "DNAReaderAdapter.h"
|
|
#include "RigLogicDNAReader.h"
|
|
#include "ControlRigEditor/Private/ControlRigEditorModule.h"
|
|
#include "Kismet2/KismetEditorUtilities.h"
|
|
#include "Rigs/RigHierarchyController.h"
|
|
#include "MetaHumanCharacterPaletteEditorModule.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "MetaHumanCharacterEditorRigLogicUnpackLibrary"
|
|
|
|
|
|
bool UMetaHumanRigLogicUnpackLibrary::UnpackRBFEvaluation(
|
|
UAnimBlueprint* AnimBlueprint,
|
|
USkeletalMesh* SkeletalMesh,
|
|
TNotNull<UObject*> GeneratedAssetOuter,
|
|
bool UnpackFingerRBFToHalfRotationControlRig,
|
|
TArray<uint16>& HalfRotationSolvers,
|
|
TArray<FMetaHumanBodyRigLogicGeneratedAsset>& OutGeneratedAssets
|
|
)
|
|
{
|
|
UAssetUserData* UserData = SkeletalMesh->GetAssetUserDataOfClass(UDNAAsset::StaticClass());
|
|
if (!UserData)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Get the dna asset from the user asset data
|
|
UDNAAsset* DNAAsset = CastChecked<UDNAAsset>(UserData);
|
|
const TSharedPtr<IDNAReader> BehaviorReader = DNAAsset->GetBehaviorReader();
|
|
|
|
// Convert from the dna coordinate space (right-handed Y-Up) to UE coordinate space (left-handed Z-Up) with the UESpaceWrapper
|
|
RigLogicDNAReader BehaviorReaderInUESpace{BehaviorReader->Unwrap()};
|
|
FDNAReader BehaviorReaderInUESpaceWrapper{&BehaviorReaderInUESpace};
|
|
|
|
// Get the neutral joint transforms for the skeleton
|
|
const FReferenceSkeleton& RefSkeleton = SkeletalMesh->GetSkeleton()->GetReferenceSkeleton();
|
|
TArray<FTransform> NeutralJointTransforms = RefSkeleton.GetRefBonePose();
|
|
|
|
// Get the neutral joint translation/rotation from the dna file
|
|
TMap<FString, UE::Math::TVector<float>> NeutralJointTranslations = {};
|
|
TMap<FString, UE::Math::TQuat<float>> NeutralJointRotations = {};
|
|
for (uint16 i = 0; i < BehaviorReaderInUESpaceWrapper.GetJointCount(); i++)
|
|
{
|
|
const dna::Vector3 Translation = BehaviorReaderInUESpace.getNeutralJointTranslation(i);
|
|
NeutralJointTranslations.Add(BehaviorReaderInUESpaceWrapper.GetJointName(i), UE::Math::TVector(Translation.x, Translation.y, Translation.z));
|
|
|
|
const dna::Vector3 Rotation = BehaviorReaderInUESpace.getNeutralJointRotation(i);
|
|
|
|
tdm::fquat q;
|
|
if (BehaviorReaderInUESpace.getRotationUnit() == dna::RotationUnit::radians) {
|
|
q = tdm::fquat{tdm::frad3{tdm::frad{Rotation.x}, tdm::frad{Rotation.y}, tdm::frad{Rotation.z}}, tdm::rot_seq::zyx};
|
|
} else {
|
|
q = tdm::fquat{tdm::frad3{tdm::frad{tdm::fdeg{Rotation.x}}, tdm::frad{tdm::fdeg{Rotation.y}}, tdm::frad{tdm::fdeg{Rotation.z}}}, tdm::rot_seq::zyx};
|
|
}
|
|
NeutralJointRotations.Add(BehaviorReaderInUESpaceWrapper.GetJointName(i), UE::Math::TQuat(q.x, q.y, q.z, q.w));
|
|
}
|
|
|
|
uint16 SolverCount = BehaviorReaderInUESpaceWrapper.GetRBFSolverCount();
|
|
|
|
OutGeneratedAssets.Reserve(SolverCount);
|
|
|
|
for (uint16 i = 0; i < BehaviorReaderInUESpaceWrapper.GetRBFSolverCount(); i++)
|
|
{
|
|
// Get the index for each driven joint
|
|
TArrayView<const uint16> JointGroupIndices = BehaviorReaderInUESpaceWrapper.GetJointGroupJointIndices(i);
|
|
|
|
const FString SolverName = BehaviorReaderInUESpaceWrapper.GetRBFSolverName(i);
|
|
// If half rotation solvers are to be unpacked to control rig, add the index and skip
|
|
if (SolverName.Contains("_half_") && UnpackFingerRBFToHalfRotationControlRig)
|
|
{
|
|
HalfRotationSolvers.Add(i);
|
|
continue;
|
|
}
|
|
FMetaHumanBodyRigLogicGeneratedAsset GeneratedAsset;
|
|
GeneratedAsset.SolverName = SolverName;
|
|
UAnimSequence* AnimSequence = NewObject<UAnimSequence>(GeneratedAssetOuter);
|
|
AnimSequence->SetSkeleton(SkeletalMesh->GetSkeleton());
|
|
GeneratedAsset.AnimSequence = AnimSequence;
|
|
|
|
// Construct the animation curve data from the transforms stored inside the dna file
|
|
TArray<FName> PoseNames = {};
|
|
TArray<const FString> DriverJointNames = {};
|
|
TArray<FName> DrivenJoints = {};
|
|
|
|
{
|
|
IAnimationDataController& Controller = AnimSequence->GetController();
|
|
|
|
Controller.OpenBracket(LOCTEXT("CreateAnimSequence", "Unpacking DNA Anim Sequence"), true);
|
|
Controller.InitializeModel();
|
|
IAnimationDataModel::FReimportScope ReimportScope(AnimSequence->GetDataModel());
|
|
// Clear any existing bone tracks in case the file already existed
|
|
Controller.RemoveAllBoneTracks();
|
|
|
|
TArrayView<const uint16> PoseIndices = BehaviorReaderInUESpaceWrapper.GetRBFSolverPoseIndices(i);
|
|
const int32 PoseCount = PoseIndices.Num();
|
|
|
|
Controller.SetNumberOfFrames(PoseCount - 1);
|
|
|
|
// Handle the creation of transforms for the poses driver transforms
|
|
TArrayView<const uint16> RawControlIndices = BehaviorReaderInUESpaceWrapper.GetRBFSolverRawControlIndices(i);
|
|
TArrayView<const float> RawControlValues = BehaviorReaderInUESpaceWrapper.GetRBFSolverRawControlValues(i);
|
|
|
|
for (const uint16 RawControlIndex : RawControlIndices)
|
|
{
|
|
const FString RawControlName = BehaviorReaderInUESpaceWrapper.GetRawControlName(RawControlIndex);
|
|
TArray<FString> Result;
|
|
RawControlName.ParseIntoArray(Result, TEXT("."), true);
|
|
const FString BoneName = Result[0];
|
|
DriverJointNames.AddUnique(BoneName);
|
|
}
|
|
|
|
const uint16 UniqueDriverJointCount = DriverJointNames.Num();
|
|
// Get the driven joint indices by mapping the raw control name back to the joint
|
|
for (uint16 ii = 0; ii < DriverJointNames.Num(); ii++)
|
|
{
|
|
FString BoneName = DriverJointNames[ii];
|
|
int32 BoneIndex = RefSkeleton.FindBoneIndex(*BoneName);
|
|
if (BoneIndex == INDEX_NONE)
|
|
{
|
|
continue;
|
|
}
|
|
FRawAnimSequenceTrack RawTrack;
|
|
RawTrack.PosKeys.Reserve(PoseCount - 1);
|
|
RawTrack.RotKeys.Reserve(PoseCount - 1);
|
|
RawTrack.ScaleKeys.Reserve(PoseCount - 1);
|
|
|
|
FTransform3f NeutralTransform = static_cast<FTransform3f>(NeutralJointTransforms[BoneIndex]);
|
|
if (!NeutralJointRotations.Contains(BoneName) || !NeutralJointTranslations.Contains(BoneName))
|
|
{
|
|
continue;
|
|
}
|
|
// Need to calculate the offset for number of driver joints and their channels
|
|
UE::Math::TQuat<float> NeutralRotation = *NeutralJointRotations.Find(BoneName);
|
|
UE::Math::TVector NeutralTranslation = *NeutralJointTranslations.Find(BoneName);
|
|
|
|
uint16 StartIndex = ii * 4;
|
|
uint16 Gap = (UniqueDriverJointCount - 1) * 4;
|
|
for (uint16 p = 0; p < PoseCount; p++)
|
|
{
|
|
RawTrack.ScaleKeys.Add(NeutralTransform.GetScale3D());
|
|
RawTrack.PosKeys.Add(NeutralTranslation);
|
|
|
|
UE::Math::TQuat<float> Rotation = {};
|
|
Rotation.X = RawControlValues[StartIndex];
|
|
Rotation.Y = RawControlValues[StartIndex + 1];
|
|
Rotation.Z = RawControlValues[StartIndex + 2];
|
|
Rotation.W = RawControlValues[StartIndex + 3];
|
|
|
|
RawTrack.RotKeys.Add(NeutralRotation * Rotation);
|
|
StartIndex += 4 + Gap;
|
|
}
|
|
|
|
Controller.AddBoneCurve(*BoneName, true);
|
|
Controller.SetBoneTrackKeys(*BoneName, RawTrack.PosKeys, RawTrack.RotKeys, RawTrack.ScaleKeys, true);
|
|
}
|
|
|
|
// Handle the creation of transforms for the poses driven transforms
|
|
TMap<const uint16, TArrayView<const uint16>> PoseJointOutputIndicesMap;
|
|
TMap<const uint16, TArrayView<const float>> PoseJointOutputValuesMap;
|
|
|
|
// Get the poses and cache their output data so that it doesn't need to be queried for each joint
|
|
for (const uint16 PoseIndex : PoseIndices)
|
|
{
|
|
PoseJointOutputIndicesMap.Add(PoseIndex, BehaviorReaderInUESpaceWrapper.GetRBFPoseJointOutputIndices(PoseIndex));
|
|
PoseJointOutputValuesMap.Add(PoseIndex, BehaviorReaderInUESpaceWrapper.GetRBFPoseJointOutputValues(PoseIndex));
|
|
}
|
|
|
|
// To generate the curve sequence in order, generate each track joint by joint
|
|
// Iterate over each joint
|
|
for (const uint16 JointIndex : JointGroupIndices)
|
|
{
|
|
// Get the bone name from the dna and map to the bone index on the skeleton
|
|
const FString BoneName = BehaviorReaderInUESpaceWrapper.GetJointName(JointIndex);
|
|
DrivenJoints.AddUnique(FName(BoneName));
|
|
const int32 BoneIndex = RefSkeleton.FindBoneIndex(*BoneName);
|
|
if (BoneIndex == INDEX_NONE)
|
|
{
|
|
continue;
|
|
}
|
|
// Create and reserve the tracks
|
|
FRawAnimSequenceTrack RawTrack;
|
|
RawTrack.PosKeys.Reserve(PoseCount - 1);
|
|
RawTrack.RotKeys.Reserve(PoseCount - 1);
|
|
RawTrack.ScaleKeys.Reserve(PoseCount - 1);
|
|
|
|
FTransform3f NeutralTransform = static_cast<FTransform3f>(NeutralJointTransforms[BoneIndex]);
|
|
if (!NeutralJointRotations.Contains(BoneName) || !NeutralJointTranslations.Contains(BoneName))
|
|
{
|
|
continue;
|
|
}
|
|
// Need to calculate the offset for number of driver joints and their channels
|
|
UE::Math::TQuat<float> NeutralRotation = *NeutralJointRotations.Find(BoneName);
|
|
UE::Math::TVector NeutralTranslation = *NeutralJointTranslations.Find(BoneName);
|
|
|
|
// Iterate over each pose and generate the animation curve data
|
|
for (const uint16 PoseIndex : PoseIndices)
|
|
{
|
|
const FString PoseName = *BehaviorReaderInUESpaceWrapper.GetRBFPoseName(PoseIndex);
|
|
PoseNames.AddUnique(FName(PoseName));
|
|
// Get the indices for the driven joints
|
|
TArrayView<const uint16> PoseJointOutputIndices = BehaviorReaderInUESpaceWrapper.GetRBFPoseJointOutputIndices(PoseIndex);
|
|
// Get the values for each driven joint
|
|
TArrayView<const float> PoseJointOutputValues = BehaviorReaderInUESpaceWrapper.GetRBFPoseJointOutputValues(PoseIndex);
|
|
|
|
FTransform3f Transform = FTransform3f();
|
|
// Create an empty map to store the curve index: curve value
|
|
TMap<uint16, float> CurveOutputMap = {};
|
|
|
|
// Generate the default values for all the joint curves as dna does not store tracks for curves with a value of 0
|
|
const uint16 EndIndex = JointIndex * 9 + 9;
|
|
for (uint16 StartIndex = JointIndex * 9; StartIndex < EndIndex; StartIndex++)
|
|
{
|
|
CurveOutputMap.Add(StartIndex, 0.0f);
|
|
}
|
|
|
|
// Add/Overwrite the curve values
|
|
for (uint16 j = 0; j < PoseJointOutputIndices.Num(); j++)
|
|
{
|
|
CurveOutputMap.Add(PoseJointOutputIndices[j], PoseJointOutputValues[j]);
|
|
}
|
|
|
|
// Construct the T/R/S values from the delta curve values + neutral values
|
|
const uint16 StartIndex = JointIndex * 9;
|
|
UE::Math::TVector Translation = {
|
|
CurveOutputMap.FindChecked(StartIndex) + NeutralTranslation.X,
|
|
CurveOutputMap.FindChecked(StartIndex + 1) + NeutralTranslation.Y,
|
|
CurveOutputMap.FindChecked(StartIndex + 2) + NeutralTranslation.Z
|
|
};
|
|
|
|
Transform.SetTranslation(Translation);
|
|
|
|
UE::Math::TRotator<float> Rotation = {};
|
|
Rotation.Roll = CurveOutputMap.FindChecked(StartIndex + 3);
|
|
Rotation.Pitch = CurveOutputMap.FindChecked(StartIndex + 4);
|
|
Rotation.Yaw = CurveOutputMap.FindChecked(StartIndex + 5);
|
|
tdm::fquat q;
|
|
|
|
if (BehaviorReaderInUESpace.getRotationUnit() == dna::RotationUnit::radians) {
|
|
q = tdm::fquat{tdm::frad3{tdm::frad{CurveOutputMap.FindChecked(StartIndex + 3)}, tdm::frad{CurveOutputMap.FindChecked(StartIndex + 4)}, tdm::frad{CurveOutputMap.FindChecked(StartIndex + 5)}}, tdm::rot_seq::zyx};
|
|
} else {
|
|
q = tdm::fquat{tdm::frad3{tdm::frad{tdm::fdeg{CurveOutputMap.FindChecked(StartIndex + 3)}}, tdm::frad{tdm::fdeg{CurveOutputMap.FindChecked(StartIndex + 4)}}, tdm::frad{tdm::fdeg{CurveOutputMap.FindChecked(StartIndex + 5)}}}, tdm::rot_seq::zyx};
|
|
}
|
|
|
|
UE::Math::TQuat RotationQuat{q.x, q.y, q.z, q.w};
|
|
|
|
Transform.SetRotation(NeutralRotation * RotationQuat);
|
|
|
|
UE::Math::TVector Scale = {
|
|
CurveOutputMap.FindChecked(StartIndex + 6),
|
|
CurveOutputMap.FindChecked(StartIndex + 7),
|
|
CurveOutputMap.FindChecked(StartIndex + 8)
|
|
};
|
|
|
|
Transform.SetScale3D(NeutralTransform.GetScale3D() + Scale);
|
|
|
|
|
|
RawTrack.ScaleKeys.Add(Transform.GetScale3D());
|
|
RawTrack.RotKeys.Add(Transform.GetRotation());
|
|
RawTrack.PosKeys.Add(Transform.GetTranslation());
|
|
}
|
|
// Add the track
|
|
Controller.AddBoneCurve(*BoneName, true);
|
|
Controller.SetBoneTrackKeys(*BoneName, RawTrack.PosKeys, RawTrack.RotKeys, RawTrack.ScaleKeys, true);
|
|
}
|
|
|
|
Controller.NotifyPopulated();
|
|
Controller.CloseBracket(true);
|
|
}
|
|
|
|
UPoseAsset* PoseAsset = NewObject<UPoseAsset>(GeneratedAssetOuter);
|
|
if (!IsValid(PoseAsset))
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Unable to create PoseAsset for solver: %s"), *SolverName);
|
|
continue;
|
|
}
|
|
|
|
PoseAsset->SetSkeleton(SkeletalMesh->GetSkeleton());
|
|
PoseAsset->SetRetargetSourceAsset(SkeletalMesh);
|
|
GeneratedAsset.PoseAsset = PoseAsset;
|
|
|
|
OutGeneratedAssets.Add(GeneratedAsset);
|
|
PoseAsset->SourceAnimation = AnimSequence;
|
|
PoseAsset->UpdatePoseFromAnimation(AnimSequence);
|
|
for (int p = 0; p < PoseNames.Num(); p++)
|
|
{
|
|
FName CurrentName = PoseAsset->GetPoseNameByIndex(p);
|
|
PoseAsset->ModifyPoseName(CurrentName, PoseNames[p]);
|
|
}
|
|
// Generate the pose asset node inside the anim blueprint
|
|
if (PoseAsset && IsValid(AnimBlueprint))
|
|
{
|
|
TArray<FName> DriverJoints = {};
|
|
for (const FString& DriverJointName : DriverJointNames)
|
|
{
|
|
DriverJoints.AddUnique(*DriverJointName);
|
|
}
|
|
|
|
UAnimGraphNode_PoseDriver* PoseDriverNode = GetPoseDriverWithTag(FName(*SolverName), AnimBlueprint);
|
|
if (!IsValid(PoseDriverNode))
|
|
{
|
|
PoseDriverNode = GetPoseDriverWithDrivers(DriverJoints, AnimBlueprint);
|
|
}
|
|
// If that still doesn't return results, create a new pose driver node and connect it
|
|
if (!IsValid(PoseDriverNode))
|
|
{
|
|
PoseDriverNode = CreatePoseDriverNode(AnimBlueprint, true);
|
|
|
|
// If creation fails, skip this solver
|
|
if (!IsValid(PoseDriverNode))
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Unable to create a pose driver node for %s"), *SolverName);
|
|
continue;
|
|
}
|
|
}
|
|
if (IsValid(PoseDriverNode))
|
|
{
|
|
// We have a valid pose driver node, time to update it with all the settings
|
|
PoseDriverNode->SetTag(FName(SolverName));
|
|
PoseDriverNode->SetSourceBones(DriverJoints);
|
|
PoseDriverNode->SetDrivingBones(DrivenJoints);
|
|
PoseDriverNode->SetAnimationAsset(PoseAsset);
|
|
PoseDriverNode->Node.bEvalFromRefPose = true;
|
|
PoseDriverNode->CopyTargetsFromPoseAsset();
|
|
FRBFParams RBFParams;
|
|
|
|
EAutomaticRadius SolverAutomaticRadius = BehaviorReaderInUESpaceWrapper.GetRBFSolverAutomaticRadius(i);
|
|
RBFParams.bAutomaticRadius = false;
|
|
if (SolverAutomaticRadius == EAutomaticRadius::On)
|
|
{
|
|
RBFParams.bAutomaticRadius = true;
|
|
}
|
|
RBFParams.SolverType = BehaviorReaderInUESpaceWrapper.GetRBFSolverType(i);
|
|
RBFParams.DistanceMethod = BehaviorReaderInUESpaceWrapper.GetRBFSolverDistanceMethod(i);
|
|
RBFParams.Function = BehaviorReaderInUESpaceWrapper.GetRBFSolverFunctionType(i);
|
|
RBFParams.NormalizeMethod = BehaviorReaderInUESpaceWrapper.GetRBFSolverNormalizeMethod(i);
|
|
ETwistAxis TwistAxis = BehaviorReaderInUESpaceWrapper.GetRBFSolverTwistAxis(i);
|
|
if (TwistAxis == ETwistAxis::X)
|
|
{
|
|
RBFParams.TwistAxis = BA_X;
|
|
}
|
|
else if (TwistAxis == ETwistAxis::Y)
|
|
{
|
|
RBFParams.TwistAxis = BA_Y;
|
|
}
|
|
else if (TwistAxis == ETwistAxis::Z)
|
|
{
|
|
RBFParams.TwistAxis = BA_Z;
|
|
}
|
|
|
|
RBFParams.Radius = BehaviorReaderInUESpaceWrapper.GetRBFSolverRadius(i);
|
|
RBFParams.WeightThreshold = BehaviorReaderInUESpaceWrapper.GetRBFSolverWeightThreshold(i);
|
|
PoseDriverNode->SetRBFParameters(RBFParams);
|
|
PoseDriverNode->SetPoseDriverSource(EPoseDriverSource::Rotation);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TObjectPtr<UControlRigBlueprint> UMetaHumanRigLogicUnpackLibrary::UnpackControlRigEvaluation(
|
|
UAnimBlueprint* AnimBlueprint,
|
|
USkeletalMesh* SkeletalMesh,
|
|
TObjectPtr<UControlRigBlueprint> ControlRig,
|
|
TNotNull<UObject*> GeneratedAssetOuter,
|
|
bool UnpackSwingTwistEvaluation,
|
|
TArray<uint16>& HalfRotationSolvers)
|
|
{
|
|
const FName AssetName = "CR_Body_Procedural";
|
|
bool bControlRigCreated = false;
|
|
if (!IsValid(ControlRig))
|
|
{
|
|
ControlRig = CastChecked<UControlRigBlueprint>(FKismetEditorUtilities::CreateBlueprint(UControlRig::StaticClass(), GeneratedAssetOuter, AssetName, BPTYPE_Normal, UControlRigBlueprint::StaticClass(), URigVMBlueprintGeneratedClass::StaticClass(), NAME_None));
|
|
FControlRigEditorModule::Get().CreateRootGraphIfRequired(ControlRig);
|
|
bControlRigCreated = true;
|
|
}
|
|
// Grab the dna user data
|
|
UAssetUserData* UserData = SkeletalMesh->GetAssetUserDataOfClass(UDNAAsset::StaticClass());
|
|
if (!UserData)
|
|
{
|
|
if (bControlRigCreated || IsValid(ControlRig))
|
|
{
|
|
return ControlRig;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UDNAAsset* DNAAsset = CastChecked<UDNAAsset>(UserData);
|
|
TSharedPtr<IDNAReader> BehaviorReader = DNAAsset->GetBehaviorReader();
|
|
|
|
RigLogicDNAReader BehaviorReaderInUESpace{BehaviorReader->Unwrap()};
|
|
FDNAReader BehaviorReaderInUESpaceWrapper{&BehaviorReaderInUESpace};
|
|
|
|
|
|
URigVMController* RigController = ControlRig->GetController();
|
|
if (!IsValid(RigController))
|
|
{
|
|
FFormatNamedArguments FormatArguments;
|
|
FormatArguments.Add(TEXT("ControlRigPath"), FText::FromString(ControlRig->GetPathName()));
|
|
|
|
FText Message = FText::Format(
|
|
LOCTEXT("RigLogicUnpackError", "Unable to unpack RigLogic to control rig. {ControlRigPath} is invalid. Asset may need saving."),
|
|
FormatArguments);
|
|
|
|
FMessageLog(UE::MetaHuman::MessageLogName).Error(Message);
|
|
return nullptr;
|
|
}
|
|
|
|
URigHierarchyController* HierarchyController = ControlRig->GetHierarchyController();
|
|
// Ensure that the hierarchy matches the incoming skeleton
|
|
HierarchyController->ImportBonesFromAsset(SkeletalMesh->GetSkeleton()->GetPathName(), "None");
|
|
|
|
// Gather the existing nodes in the graph
|
|
URigVMGraph* TopLevelGraph = RigController->GetTopLevelGraph();
|
|
TArray<URigVMNode*> GraphNodes = TopLevelGraph->GetNodes();
|
|
TArray<URigVMNode*> TwistNodes;
|
|
TArray<URigVMNode*> SwingNodes;
|
|
TArray<URigVMNode*> HalfRotationNodes;
|
|
|
|
TArray<URigVMNode*> GeneratedNodes;
|
|
|
|
for (URigVMNode* GraphNode : GraphNodes)
|
|
{
|
|
if (GraphNode->GetNodeTitle() == "ComputeTwist")
|
|
{
|
|
TwistNodes.Add(GraphNode);
|
|
}
|
|
else if (GraphNode->GetNodeTitle() == "ComputeSwing")
|
|
{
|
|
SwingNodes.Add(GraphNode);
|
|
}
|
|
else if (GraphNode->GetNodeTitle() == "ComputeHalfFingers")
|
|
{
|
|
HalfRotationNodes.Add(GraphNode);
|
|
}
|
|
}
|
|
|
|
if (UnpackSwingTwistEvaluation)
|
|
{
|
|
// Generate the twist nodes from the dna data
|
|
for (uint16 i = 0; i < BehaviorReaderInUESpaceWrapper.GetTwistCount(); i++)
|
|
{
|
|
TArrayView<const uint16> TwistControlIndices = BehaviorReaderInUESpaceWrapper.GetTwistInputControlIndices(i);
|
|
if (TwistControlIndices.IsEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
// Get the input joint name
|
|
const FString RawControlName = BehaviorReaderInUESpaceWrapper.GetRawControlName(TwistControlIndices[0]);
|
|
TArray<FString> SplitName;
|
|
RawControlName.ParseIntoArray(SplitName, TEXT("."), true);
|
|
if (SplitName.IsEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FString InputJointName = SplitName[0];
|
|
|
|
// Get the output joint names
|
|
TArrayView<const uint16> OutputJointIndices = BehaviorReaderInUESpaceWrapper.GetTwistOutputJointIndices(i);
|
|
TArray<FString> OutputJointNames;
|
|
for (const uint16 JointIndex : OutputJointIndices)
|
|
{
|
|
OutputJointNames.AddUnique(BehaviorReaderInUESpaceWrapper.GetJointName(JointIndex));
|
|
}
|
|
|
|
// Get the blend values
|
|
TArrayView<const float> BlendValues = BehaviorReaderInUESpaceWrapper.GetTwistBlendWeights(i);
|
|
const ETwistAxis TwistAxis = BehaviorReaderInUESpaceWrapper.GetTwistSetupTwistAxis(i);
|
|
URigVMNode* TwistGraphNode = nullptr;
|
|
|
|
// Try to get an existing twist node
|
|
if (!TwistNodes.IsEmpty())
|
|
{
|
|
for (URigVMNode* GraphNode : TwistNodes)
|
|
{
|
|
if (const URigVMPin* InputBonePin = GraphNode->FindPin(TEXT("InputBone")))
|
|
{
|
|
if (const URigVMPin* NamePin = InputBonePin->FindSubPin(TEXT("Name")))
|
|
{
|
|
if (NamePin->GetDefaultValue() != InputJointName)
|
|
{
|
|
continue;
|
|
}
|
|
TwistGraphNode = GraphNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Create the node
|
|
if (!IsValid(TwistGraphNode))
|
|
{
|
|
URigVMFunctionReferenceNode* NewNode = RigController->AddExternalFunctionReferenceNode(
|
|
"/MetaHumanCharacter/Controls/CR_MH_Function_Library.CR_MH_Function_Library", "ComputeTwist");
|
|
TwistGraphNode = CastChecked<URigVMNode>(NewNode);
|
|
GeneratedNodes.Add(TwistGraphNode);
|
|
}
|
|
|
|
if (!IsValid(TwistGraphNode))
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Unable to create Twist setup for %s"), *InputJointName);
|
|
continue;
|
|
}
|
|
// Set the node pin values from the dna data
|
|
if (URigVMPin* InputBonePin = TwistGraphNode->FindPin(TEXT("InputBone")))
|
|
{
|
|
if (URigVMPin* TypePin = InputBonePin->FindSubPin(TEXT("Type")))
|
|
{
|
|
RigController->SetPinDefaultValue(TypePin->GetPinPath(), TEXT("Bone"));
|
|
}
|
|
if (URigVMPin* NamePin = InputBonePin->FindSubPin(TEXT("Name")))
|
|
{
|
|
RigController->SetPinDefaultValue(NamePin->GetPinPath(), InputJointName);
|
|
}
|
|
}
|
|
if (URigVMPin* TwistBonesPin = TwistGraphNode->FindPin(TEXT("TwistBones")))
|
|
{
|
|
RigController->ClearArrayPin(TwistBonesPin->GetPinPath());
|
|
|
|
for (uint16 j = 0; j < OutputJointNames.Num(); j++)
|
|
{
|
|
FString PinPathRoot = TwistBonesPin->GetPinPath() + "." + FString::FromInt(j);
|
|
FString TypePinPath = PinPathRoot + ".Type";
|
|
FString NamePinPath = PinPathRoot + ".Name";
|
|
RigController->AddArrayPin(TwistBonesPin->GetPinPath());
|
|
RigController->SetPinDefaultValue(TypePinPath, "Bone");
|
|
RigController->SetPinDefaultValue(NamePinPath, OutputJointNames[j]);
|
|
}
|
|
}
|
|
if (URigVMPin* TwistBlendPin = TwistGraphNode->FindPin(TEXT("TwistBlend")))
|
|
{
|
|
RigController->ClearArrayPin(TwistBlendPin->GetPinPath());
|
|
|
|
for (uint16 j = 0; j < BlendValues.Num(); j++)
|
|
{
|
|
FString PinPathRoot = TwistBlendPin->GetPinPath() + "." + FString::FromInt(j);
|
|
RigController->AddArrayPin(TwistBlendPin->GetPinPath());
|
|
RigController->SetPinDefaultValue(PinPathRoot, FString::SanitizeFloat(BlendValues[j]));
|
|
}
|
|
}
|
|
if (URigVMPin* TwistAxisPin = TwistGraphNode->FindPin(TEXT("TwistAxis")))
|
|
{
|
|
if (TwistAxis == ETwistAxis::X)
|
|
{
|
|
RigController->SetPinDefaultValue(TwistAxisPin->GetPinPath(), "(X=1.0, Y=0.0, Z=0.0)");
|
|
}
|
|
else if (TwistAxis == ETwistAxis::Y)
|
|
{
|
|
RigController->SetPinDefaultValue(TwistAxisPin->GetPinPath(), "(X=0.0, Y=1.0, Z=0.0)");
|
|
}
|
|
else
|
|
{
|
|
RigController->SetPinDefaultValue(TwistAxisPin->GetPinPath(), "(X=0.0, Y=0.0, Z=1.0)");
|
|
}
|
|
}
|
|
|
|
if (URigVMPin* TwistFromEndPin = TwistGraphNode->FindPin(TEXT("TwistFromEnd")))
|
|
{
|
|
URigHierarchy* Hierarchy = HierarchyController->GetHierarchy();
|
|
FRigElementKey InputBone;
|
|
InputBone.Type = ERigElementType::Bone;
|
|
InputBone.Name = *InputJointName;
|
|
TArray<FRigElementKey> Children = Hierarchy->GetChildren(InputBone, true);
|
|
bool bMatch = false;
|
|
for (FRigElementKey Child : Children)
|
|
{
|
|
if (OutputJointNames.Contains(Child.Name))
|
|
{
|
|
bMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bMatch)
|
|
{
|
|
RigController->SetPinDefaultValue(TwistFromEndPin->GetPinPath(), "true");
|
|
}
|
|
}
|
|
}
|
|
// Build the swing nodes
|
|
for (uint16 i = 0; i < BehaviorReaderInUESpaceWrapper.GetSwingCount(); i++)
|
|
{
|
|
TArrayView<const uint16> SwingControlIndices = BehaviorReaderInUESpaceWrapper.GetSwingInputControlIndices(i);
|
|
if (SwingControlIndices.IsEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
// Get the input joint name
|
|
const FString RawControlName = BehaviorReaderInUESpaceWrapper.GetRawControlName(SwingControlIndices[0]);
|
|
TArray<FString> SplitName;
|
|
RawControlName.ParseIntoArray(SplitName, TEXT("."), true);
|
|
if (SplitName.IsEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FString InputJointName = SplitName[0];
|
|
|
|
// Get the output joint names
|
|
TArrayView<const uint16> OutputJointIndices = BehaviorReaderInUESpaceWrapper.GetSwingOutputJointIndices(i);
|
|
TArray<FString> OutputJointNames;
|
|
for (const uint16 JointIndex : OutputJointIndices)
|
|
{
|
|
OutputJointNames.AddUnique(BehaviorReaderInUESpaceWrapper.GetJointName(JointIndex));
|
|
}
|
|
|
|
// Get the blend values
|
|
TArrayView<const float> BlendValues = BehaviorReaderInUESpaceWrapper.GetSwingBlendWeights(i);
|
|
ETwistAxis TwistAxis = BehaviorReaderInUESpaceWrapper.GetSwingSetupTwistAxis(i);
|
|
URigVMNode* SwingGraphNode = nullptr;
|
|
|
|
if (!SwingNodes.IsEmpty())
|
|
{
|
|
for (URigVMNode* GraphNode : SwingNodes)
|
|
{
|
|
if (URigVMPin* InputBonePin = GraphNode->FindPin(TEXT("InputBone")))
|
|
{
|
|
if (URigVMPin* NamePin = InputBonePin->FindSubPin(TEXT("Name")))
|
|
{
|
|
if (NamePin->GetDefaultValue() != InputJointName)
|
|
{
|
|
continue;
|
|
}
|
|
SwingGraphNode = GraphNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!IsValid(SwingGraphNode))
|
|
{
|
|
URigVMFunctionReferenceNode* NewNode = RigController->AddExternalFunctionReferenceNode(
|
|
"/MetaHumanCharacter/Controls/CR_MH_Function_Library.CR_MH_Function_Library", "ComputeSwing");
|
|
SwingGraphNode = CastChecked<URigVMNode>(NewNode);
|
|
GeneratedNodes.Add(SwingGraphNode);
|
|
}
|
|
|
|
if (!IsValid(SwingGraphNode))
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Unable to create Swing setup for %s"), *InputJointName);
|
|
continue;
|
|
}
|
|
|
|
if (URigVMPin* InputBonePin = SwingGraphNode->FindPin(TEXT("InputBone")))
|
|
{
|
|
if (URigVMPin* TypePin = InputBonePin->FindSubPin(TEXT("Type")))
|
|
{
|
|
RigController->SetPinDefaultValue(TypePin->GetPinPath(), TEXT("Bone"));
|
|
}
|
|
if (URigVMPin* NamePin = InputBonePin->FindSubPin(TEXT("Name")))
|
|
{
|
|
RigController->SetPinDefaultValue(NamePin->GetPinPath(), InputJointName);
|
|
}
|
|
}
|
|
if (URigVMPin* CorrectiveBonePin = SwingGraphNode->FindPin(TEXT("CorrectiveBone")))
|
|
{
|
|
for (uint16 j = 0; j < OutputJointNames.Num();)
|
|
{
|
|
FString PinPathRoot = CorrectiveBonePin->GetPinPath();
|
|
FString TypePinPath = PinPathRoot + ".Type";
|
|
FString NamePinPath = PinPathRoot + ".Name";
|
|
RigController->SetPinDefaultValue(TypePinPath, "Bone");
|
|
RigController->SetPinDefaultValue(NamePinPath, OutputJointNames[j]);
|
|
break;
|
|
}
|
|
}
|
|
if (URigVMPin* SwingBlendPin = SwingGraphNode->FindPin(TEXT("SwingBlend")))
|
|
{
|
|
for (uint16 j = 0; j < BlendValues.Num();)
|
|
{
|
|
FString PinPathRoot = SwingBlendPin->GetPinPath();
|
|
RigController->SetPinDefaultValue(PinPathRoot, FString::SanitizeFloat(BlendValues[j]));
|
|
break;
|
|
}
|
|
}
|
|
if (URigVMPin* TwistAxisPin = SwingGraphNode->FindPin(TEXT("TwistAxis")))
|
|
{
|
|
if (TwistAxis == ETwistAxis::X)
|
|
{
|
|
RigController->SetPinDefaultValue(TwistAxisPin->GetPinPath(), "(X=1.0, Y=0.0, Z=0.0)");
|
|
}
|
|
else if (TwistAxis == ETwistAxis::Y)
|
|
{
|
|
RigController->SetPinDefaultValue(TwistAxisPin->GetPinPath(), "(X=0.0, Y=1.0, Z=0.0)");
|
|
}
|
|
else
|
|
{
|
|
RigController->SetPinDefaultValue(TwistAxisPin->GetPinPath(), "(X=0.0, Y=0.0, Z=1.0)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Build the half rotation setup for fingers
|
|
if (!HalfRotationSolvers.IsEmpty())
|
|
{
|
|
TArray<FString> DriverJointNames;
|
|
|
|
for (uint16 SolverIndex : HalfRotationSolvers)
|
|
{
|
|
// Get the index for each driven joint
|
|
TArrayView<const uint16> JointGroupIndices = BehaviorReaderInUESpaceWrapper.GetJointGroupJointIndices(SolverIndex);
|
|
|
|
for (const uint16 JointIndex : JointGroupIndices)
|
|
{
|
|
FString JointName = BehaviorReaderInUESpaceWrapper.GetRawControlName(JointIndex);
|
|
// Get the bone name from the dna and map to the bone index on the skeleton
|
|
FString BoneName = BehaviorReaderInUESpaceWrapper.GetJointName(JointIndex);
|
|
DriverJointNames.AddUnique(BoneName);
|
|
}
|
|
}
|
|
|
|
URigVMNode* HalfRotationNode = nullptr;
|
|
if (HalfRotationNodes.IsEmpty())
|
|
{
|
|
URigVMFunctionReferenceNode* NewNode = RigController->AddExternalFunctionReferenceNode(
|
|
"/MetaHumanCharacter/Controls/CR_MH_Function_Library.CR_MH_Function_Library", "ComputeHalfFingers");
|
|
HalfRotationNode = CastChecked<URigVMNode>(NewNode);
|
|
GeneratedNodes.Add(HalfRotationNode);
|
|
}
|
|
else
|
|
{
|
|
HalfRotationNode = HalfRotationNodes[0];
|
|
}
|
|
if (IsValid(HalfRotationNode))
|
|
{
|
|
if (URigVMPin* HalfBonesPin = HalfRotationNode->FindPin(TEXT("HalfBones")))
|
|
{
|
|
RigController->ClearArrayPin(HalfBonesPin->GetPinPath());
|
|
|
|
for (uint16 j = 0; j < DriverJointNames.Num(); j++)
|
|
{
|
|
FString PinPathRoot = HalfBonesPin->GetPinPath() + "." + FString::FromInt(j);
|
|
FString TypePinPath = PinPathRoot + ".Type";
|
|
FString NamePinPath = PinPathRoot + ".Name";
|
|
RigController->AddArrayPin(HalfBonesPin->GetPinPath());
|
|
RigController->SetPinDefaultValue(TypePinPath, "Bone");
|
|
RigController->SetPinDefaultValue(NamePinPath, DriverJointNames[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Connect up all the newly generated nodes
|
|
URigVMNode* ExecutionNode = TopLevelGraph->FindNodeByName("BeginExecution");
|
|
if (!IsValid(ExecutionNode))
|
|
{
|
|
ExecutionNode = RigController->AddUnitNodeFromStructPath("/Script/ControlRig.RigUnit_BeginExecution", "Execute", FVector2D::ZeroVector,
|
|
"BeginExecution");
|
|
}
|
|
|
|
URigVMPin* OutputPin = ExecutionNode->FindExecutePin();
|
|
TArray<URigVMLink*> Links = ExecutionNode->FindExecutePin()->GetLinks();
|
|
URigVMPin* ExistingExecutePin = nullptr;
|
|
if (Links.Num() > 0)
|
|
{
|
|
ExistingExecutePin = Links[0]->GetTargetPin();
|
|
RigController->BreakLink(OutputPin, ExistingExecutePin);
|
|
}
|
|
|
|
|
|
for (URigVMNode* NewNode : GeneratedNodes)
|
|
{
|
|
URigVMPin* InputPin = NewNode->FindExecutePin();
|
|
RigController->AddLink(OutputPin, InputPin);
|
|
OutputPin = InputPin;
|
|
}
|
|
if (IsValid(ExistingExecutePin))
|
|
{
|
|
RigController->AddLink(OutputPin, ExistingExecutePin);
|
|
}
|
|
|
|
if (IsValid(AnimBlueprint))
|
|
{
|
|
// Add the control rig to the anim blueprint
|
|
UClass* ControlRigClass = ControlRig->CreateControlRig()->GetClass();
|
|
for (UEdGraph* Graph : AnimBlueprint->FunctionGraphs)
|
|
{
|
|
if (UAnimationGraph* AnimGraph = Cast<UAnimationGraph>(Graph))
|
|
{
|
|
TArray<UAnimGraphNode_ControlRig*> ControlRigNodes;
|
|
|
|
AnimGraph->GetNodesOfClass(ControlRigNodes);
|
|
UAnimGraphNode_ControlRig* ControlRigNode = nullptr;
|
|
// Check for an existing control rig
|
|
for (UAnimGraphNode_ControlRig* Node : ControlRigNodes)
|
|
{
|
|
if (Node->GetTag() == AssetName)
|
|
{
|
|
ControlRigNode = Node;
|
|
}
|
|
else if (Node->Node.GetControlRigClass()->GetPathName() == ControlRig->GetPathName())
|
|
{
|
|
ControlRigNode = Node;
|
|
}
|
|
}
|
|
|
|
if (ControlRigNode == nullptr)
|
|
{
|
|
// Create a new control rig
|
|
UEdGraphNode* UEdControlRigNode = NewObject<UAnimGraphNode_ControlRig>(AnimGraph);
|
|
ControlRigNode = Cast<UAnimGraphNode_ControlRig>(UEdControlRigNode);
|
|
AnimGraph->AddNode(UEdControlRigNode, true);
|
|
ControlRigNode->CreateNewGuid();
|
|
ControlRigNode->PostPlacedNewNode();
|
|
ControlRigNode->AllocateDefaultPins();
|
|
TArray<UAnimGraphNode_Root*> ResultGraphNodes;
|
|
AnimGraph->GetNodesOfClass(ResultGraphNodes);
|
|
ControlRigNode->SetTag(AssetName);
|
|
if (!ResultGraphNodes.IsEmpty())
|
|
{
|
|
UAnimGraphNode_Root* ResultGraphNode = ResultGraphNodes[0];
|
|
|
|
for (UEdGraphPin* Pin : ControlRigNode->Pins)
|
|
{
|
|
switch (Pin->Direction)
|
|
{
|
|
case EGPD_Input:
|
|
// Make link to the previous input or start node
|
|
for (UEdGraphPin* ResultPin : ResultGraphNode->Pins)
|
|
{
|
|
TArray<UEdGraphPin*> LinkedPins = ResultPin->LinkedTo;
|
|
for (UEdGraphPin* LinkedPin : LinkedPins)
|
|
{
|
|
ResultPin->BreakLinkTo(LinkedPin);
|
|
}
|
|
for (UEdGraphPin* LinkedPin : LinkedPins)
|
|
{
|
|
LinkedPin->MakeLinkTo(Pin);
|
|
// Simple auto layout
|
|
ControlRigNode->NodePosX = LinkedPin->GetOwningNode()->NodePosX + ControlRigNode->NodeWidth + 200;
|
|
ControlRigNode->NodePosY = LinkedPin->GetOwningNode()->NodePosY;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EGPD_Output:
|
|
// Make link to the result node
|
|
for (UEdGraphPin* ResultPin : ResultGraphNode->Pins)
|
|
{
|
|
Pin->MakeLinkTo(ResultPin);
|
|
}
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsValid(ControlRigNode))
|
|
{
|
|
ControlRigNode->Node.SetControlRigClass(ControlRigClass);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (bControlRigCreated)
|
|
{
|
|
return ControlRig;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UAnimGraphNode_PoseDriver* UMetaHumanRigLogicUnpackLibrary::CreatePoseDriverNode(const UAnimBlueprint* AnimBlueprint, const bool bAutoConnect)
|
|
{
|
|
for (UEdGraph* Graph : AnimBlueprint->FunctionGraphs)
|
|
{
|
|
if (UAnimationGraph* AnimGraph = Cast<UAnimationGraph>(Graph))
|
|
{
|
|
// Create a pose driver graph node
|
|
UEdGraphNode* UEdPoseDriverNode = NewObject<UAnimGraphNode_PoseDriver>(AnimGraph);
|
|
UAnimGraphNode_PoseDriver* PoseDriverNode = Cast<UAnimGraphNode_PoseDriver>(UEdPoseDriverNode);
|
|
|
|
// Add the node to the graph
|
|
AnimGraph->AddNode(UEdPoseDriverNode, true);
|
|
PoseDriverNode->CreateNewGuid();
|
|
PoseDriverNode->PostPlacedNewNode();
|
|
PoseDriverNode->AllocateDefaultPins();
|
|
if (bAutoConnect)
|
|
{
|
|
// Try to find a results graph node
|
|
TArray<UAnimGraphNode_Root*> ResultGraphNodes;
|
|
AnimGraph->GetNodesOfClass(ResultGraphNodes);
|
|
if (ResultGraphNodes.IsEmpty())
|
|
{
|
|
return PoseDriverNode;
|
|
}
|
|
|
|
UAnimGraphNode_Root* ResultGraphNode = ResultGraphNodes[0];
|
|
|
|
for (UEdGraphPin* Pin : PoseDriverNode->Pins)
|
|
{
|
|
switch (Pin->Direction)
|
|
{
|
|
case EGPD_Input:
|
|
// Make link to the previous input or start node
|
|
for (UEdGraphPin* ResultPin : ResultGraphNode->Pins)
|
|
{
|
|
TArray<UEdGraphPin*> LinkedPins = ResultPin->LinkedTo;
|
|
for (UEdGraphPin* LinkedPin : LinkedPins)
|
|
{
|
|
ResultPin->BreakLinkTo(LinkedPin);
|
|
}
|
|
for (UEdGraphPin* LinkedPin : LinkedPins)
|
|
{
|
|
LinkedPin->MakeLinkTo(Pin);
|
|
// Simple auto layout
|
|
PoseDriverNode->NodePosX = LinkedPin->GetOwningNode()->NodePosX + PoseDriverNode->NodeWidth + 200;
|
|
PoseDriverNode->NodePosY = LinkedPin->GetOwningNode()->NodePosY;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EGPD_Output:
|
|
// Make link to the result node
|
|
for (UEdGraphPin* ResultPin : ResultGraphNode->Pins)
|
|
{
|
|
Pin->MakeLinkTo(ResultPin);
|
|
}
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return PoseDriverNode;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UAnimGraphNode_PoseDriver* UMetaHumanRigLogicUnpackLibrary::GetPoseDriverWithDrivers(const TArray<FName>& DriverJointNames,
|
|
const UAnimBlueprint* AnimBlueprint)
|
|
{
|
|
for (UEdGraph* Graph : AnimBlueprint->FunctionGraphs)
|
|
{
|
|
if (const UAnimationGraph* AnimGraph = Cast<UAnimationGraph>(Graph))
|
|
{
|
|
TArray<UAnimGraphNode_PoseDriver*> PoseDriverNodes;
|
|
AnimGraph->GetNodesOfClass(PoseDriverNodes);
|
|
for (UAnimGraphNode_PoseDriver* Node : PoseDriverNodes)
|
|
{
|
|
TArray<FName> SourceBoneNames;
|
|
Node->GetSourceBoneNames(SourceBoneNames);
|
|
if (SourceBoneNames == DriverJointNames)
|
|
{
|
|
return Node;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UAnimGraphNode_PoseDriver* UMetaHumanRigLogicUnpackLibrary::GetPoseDriverWithTag(const FName& DriverTag, const UAnimBlueprint* AnimBlueprint)
|
|
{
|
|
for (UEdGraph* Graph : AnimBlueprint->FunctionGraphs)
|
|
{
|
|
if (const UAnimationGraph* AnimGraph = Cast<UAnimationGraph>(Graph))
|
|
{
|
|
TArray<UAnimGraphNode_PoseDriver*> PoseDriverNodes;
|
|
AnimGraph->GetNodesOfClass(PoseDriverNodes);
|
|
for (UAnimGraphNode_PoseDriver* Node : PoseDriverNodes)
|
|
{
|
|
if (Node->GetTag() == DriverTag)
|
|
{
|
|
return Node;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|