// 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 GeneratedAssetOuter, bool UnpackFingerRBFToHalfRotationControlRig, TArray& HalfRotationSolvers, TArray& OutGeneratedAssets ) { UAssetUserData* UserData = SkeletalMesh->GetAssetUserDataOfClass(UDNAAsset::StaticClass()); if (!UserData) { return false; } // Get the dna asset from the user asset data UDNAAsset* DNAAsset = CastChecked(UserData); const TSharedPtr 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 NeutralJointTransforms = RefSkeleton.GetRefBonePose(); // Get the neutral joint translation/rotation from the dna file TMap> NeutralJointTranslations = {}; TMap> 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 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(GeneratedAssetOuter); AnimSequence->SetSkeleton(SkeletalMesh->GetSkeleton()); GeneratedAsset.AnimSequence = AnimSequence; // Construct the animation curve data from the transforms stored inside the dna file TArray PoseNames = {}; TArray DriverJointNames = {}; TArray 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 PoseIndices = BehaviorReaderInUESpaceWrapper.GetRBFSolverPoseIndices(i); const int32 PoseCount = PoseIndices.Num(); Controller.SetNumberOfFrames(PoseCount - 1); // Handle the creation of transforms for the poses driver transforms TArrayView RawControlIndices = BehaviorReaderInUESpaceWrapper.GetRBFSolverRawControlIndices(i); TArrayView RawControlValues = BehaviorReaderInUESpaceWrapper.GetRBFSolverRawControlValues(i); for (const uint16 RawControlIndex : RawControlIndices) { const FString RawControlName = BehaviorReaderInUESpaceWrapper.GetRawControlName(RawControlIndex); TArray 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(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 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 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> PoseJointOutputIndicesMap; TMap> 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(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 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 PoseJointOutputIndices = BehaviorReaderInUESpaceWrapper.GetRBFPoseJointOutputIndices(PoseIndex); // Get the values for each driven joint TArrayView PoseJointOutputValues = BehaviorReaderInUESpaceWrapper.GetRBFPoseJointOutputValues(PoseIndex); FTransform3f Transform = FTransform3f(); // Create an empty map to store the curve index: curve value TMap 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 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(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 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 UMetaHumanRigLogicUnpackLibrary::UnpackControlRigEvaluation( UAnimBlueprint* AnimBlueprint, USkeletalMesh* SkeletalMesh, TObjectPtr ControlRig, TNotNull GeneratedAssetOuter, bool UnpackSwingTwistEvaluation, TArray& HalfRotationSolvers) { const FName AssetName = "CR_Body_Procedural"; bool bControlRigCreated = false; if (!IsValid(ControlRig)) { ControlRig = CastChecked(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(UserData); TSharedPtr 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 GraphNodes = TopLevelGraph->GetNodes(); TArray TwistNodes; TArray SwingNodes; TArray HalfRotationNodes; TArray 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 TwistControlIndices = BehaviorReaderInUESpaceWrapper.GetTwistInputControlIndices(i); if (TwistControlIndices.IsEmpty()) { continue; } // Get the input joint name const FString RawControlName = BehaviorReaderInUESpaceWrapper.GetRawControlName(TwistControlIndices[0]); TArray SplitName; RawControlName.ParseIntoArray(SplitName, TEXT("."), true); if (SplitName.IsEmpty()) { continue; } const FString InputJointName = SplitName[0]; // Get the output joint names TArrayView OutputJointIndices = BehaviorReaderInUESpaceWrapper.GetTwistOutputJointIndices(i); TArray OutputJointNames; for (const uint16 JointIndex : OutputJointIndices) { OutputJointNames.AddUnique(BehaviorReaderInUESpaceWrapper.GetJointName(JointIndex)); } // Get the blend values TArrayView 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(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 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 SwingControlIndices = BehaviorReaderInUESpaceWrapper.GetSwingInputControlIndices(i); if (SwingControlIndices.IsEmpty()) { continue; } // Get the input joint name const FString RawControlName = BehaviorReaderInUESpaceWrapper.GetRawControlName(SwingControlIndices[0]); TArray SplitName; RawControlName.ParseIntoArray(SplitName, TEXT("."), true); if (SplitName.IsEmpty()) { continue; } const FString InputJointName = SplitName[0]; // Get the output joint names TArrayView OutputJointIndices = BehaviorReaderInUESpaceWrapper.GetSwingOutputJointIndices(i); TArray OutputJointNames; for (const uint16 JointIndex : OutputJointIndices) { OutputJointNames.AddUnique(BehaviorReaderInUESpaceWrapper.GetJointName(JointIndex)); } // Get the blend values TArrayView 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(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 DriverJointNames; for (uint16 SolverIndex : HalfRotationSolvers) { // Get the index for each driven joint TArrayView 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(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 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(Graph)) { TArray 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(AnimGraph); ControlRigNode = Cast(UEdControlRigNode); AnimGraph->AddNode(UEdControlRigNode, true); ControlRigNode->CreateNewGuid(); ControlRigNode->PostPlacedNewNode(); ControlRigNode->AllocateDefaultPins(); TArray 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 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(Graph)) { // Create a pose driver graph node UEdGraphNode* UEdPoseDriverNode = NewObject(AnimGraph); UAnimGraphNode_PoseDriver* PoseDriverNode = Cast(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 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 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& DriverJointNames, const UAnimBlueprint* AnimBlueprint) { for (UEdGraph* Graph : AnimBlueprint->FunctionGraphs) { if (const UAnimationGraph* AnimGraph = Cast(Graph)) { TArray PoseDriverNodes; AnimGraph->GetNodesOfClass(PoseDriverNodes); for (UAnimGraphNode_PoseDriver* Node : PoseDriverNodes) { TArray 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(Graph)) { TArray PoseDriverNodes; AnimGraph->GetNodesOfClass(PoseDriverNodes); for (UAnimGraphNode_PoseDriver* Node : PoseDriverNodes) { if (Node->GetTag() == DriverTag) { return Node; } } } } return nullptr; } #undef LOCTEXT_NAMESPACE