377 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			377 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 
								 | 
							
								---
							 | 
						|||
| 
								 | 
							
								title: UE5商城动画重定向插件笔记
							 | 
						|||
| 
								 | 
							
								date: 2023-09-05 12:02:29
							 | 
						|||
| 
								 | 
							
								excerpt: 
							 | 
						|||
| 
								 | 
							
								tags:
							 | 
						|||
| 
								 | 
							
								  - AnimationRetargeting
							 | 
						|||
| 
								 | 
							
								rating: ⭐
							 | 
						|||
| 
								 | 
							
								---
							 | 
						|||
| 
								 | 
							
								# MixamoAnimationRetargeting
							 | 
						|||
| 
								 | 
							
								主要逻辑位于:FMixamoSkeletonRetargeter
							 | 
						|||
| 
								 | 
							
								- UE4MannequinToMixamo_BoneNamesMapping
							 | 
						|||
| 
								 | 
							
								- UE4MannequinToMixamo_ChainNamesMapping
							 | 
						|||
| 
								 | 
							
								- UE5MannequinToMixamo_BoneNamesMapping
							 | 
						|||
| 
								 | 
							
								- UE5MannequinToMixamo_ChainNamesMapping
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								重定向逻辑位于:FMixamoSkeletonRetargeter::Retarget()
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## 生成TPose
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								static const TArray<FName> Mixamo_PreserveComponentSpacePose_BoneNames = {  
							 | 
						|||
| 
								 | 
							
								    "Head",  
							 | 
						|||
| 
								 | 
							
								    "LeftToeBase",  
							 | 
						|||
| 
								 | 
							
								    "RightToeBase"  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								#ifdef MAR_UPPERARMS_PRESERVECS_EXPERIMENTAL_ENABLE_  
							 | 
						|||
| 
								 | 
							
								    ,"RightShoulder"  
							 | 
						|||
| 
								 | 
							
								    ,"RightArm"    ,"LeftShoulder"    ,"LeftArm"#endif  
							 | 
						|||
| 
								 | 
							
								};
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								static const TArray<TPair<FName, FName>> Mixamo_ParentChildBoneNamesToBypassOneChildConstraint = {  
							 | 
						|||
| 
								 | 
							
								    {"LeftUpLeg", "LeftLeg"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftLeg", "LeftFoot"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftFoot", "LeftToeBase"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftToeBase", "LeftToe_End"},  
							 | 
						|||
| 
								 | 
							
								    {"RightUpLeg", "RightLeg"},  
							 | 
						|||
| 
								 | 
							
								    {"RightLeg", "RightFoot"},  
							 | 
						|||
| 
								 | 
							
								    {"RightFoot", "RightToeBase"},  
							 | 
						|||
| 
								 | 
							
								    {"RightToeBase", "RightToe_End"},  
							 | 
						|||
| 
								 | 
							
								    {"Hips", "Spine"}, // Heuristic to try to align better the part.  
							 | 
						|||
| 
								 | 
							
								    {"Spine", "Spine1"},  
							 | 
						|||
| 
								 | 
							
								    {"Spine1", "Spine2"},  
							 | 
						|||
| 
								 | 
							
								    {"Spine2", "Neck"},    // Heuristic to try to align better the part.  
							 | 
						|||
| 
								 | 
							
								    {"Neck", "Head"},  
							 | 
						|||
| 
								 | 
							
								    {"Head", "HeadTop_End"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftShoulder", "LeftArm"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftArm", "LeftForeArm"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftForeArm", "LeftHand"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHand", "LeftHandMiddle1"},   // Heuristic to try to align better the part.  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandIndex1", "LeftHandIndex2"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandIndex2", "LeftHandIndex3"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandIndex3", "LeftHandIndex4"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandMiddle1", "LeftHandMiddle2"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandMiddle2", "LeftHandMiddle3"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandMiddle3", "LeftHandMiddle4"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandPinky1", "LeftHandPinky2"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandPinky2", "LeftHandPinky3"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandPinky3", "LeftHandPinky4"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandRing1", "LeftHandRing2"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandRing2", "LeftHandRing3"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandRing3", "LeftHandRing4"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandThumb1", "LeftHandThumb2"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandThumb2", "LeftHandThumb3"},  
							 | 
						|||
| 
								 | 
							
								    {"LeftHandThumb3", "LeftHandThumb4"},  
							 | 
						|||
| 
								 | 
							
								    {"RightShoulder", "RightArm"},  
							 | 
						|||
| 
								 | 
							
								    {"RightArm", "RightForeArm"},  
							 | 
						|||
| 
								 | 
							
								    {"RightForeArm", "RightHand"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHand", "RightHandMiddle1"}, // Heuristic to try to align better the part.  
							 | 
						|||
| 
								 | 
							
								    {"RightHandIndex1", "RightHandIndex2"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandIndex2", "RightHandIndex3"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandIndex3", "RightHandIndex4"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandMiddle1", "RightHandMiddle2"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandMiddle2", "RightHandMiddle3"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandMiddle3", "RightHandMiddle4"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandPinky1", "RightHandPinky2"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandPinky2", "RightHandPinky3"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandPinky3", "RightHandPinky4"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandRing1", "RightHandRing2"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandRing2", "RightHandRing3"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandRing3", "RightHandRing4"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandThumb1", "RightHandThumb2"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandThumb2", "RightHandThumb3"},  
							 | 
						|||
| 
								 | 
							
								    {"RightHandThumb3", "RightHandThumb4"}  
							 | 
						|||
| 
								 | 
							
								};
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								RetargetBasePose(  
							 | 
						|||
| 
								 | 
							
								    SkeletalMeshes,  
							 | 
						|||
| 
								 | 
							
								    ReferenceSkeleton,  
							 | 
						|||
| 
								 | 
							
								    Mixamo_PreserveComponentSpacePose_BoneNames,  
							 | 
						|||
| 
								 | 
							
								    UEMannequinToMixamo_BoneNamesMapping.GetInverseMapper(),  
							 | 
						|||
| 
								 | 
							
								    Mixamo_ParentChildBoneNamesToBypassOneChildConstraint,  
							 | 
						|||
| 
								 | 
							
								    /*bApplyPoseToRetargetBasePose=*/true,  
							 | 
						|||
| 
								 | 
							
								    UIKRetargeterController::GetController(IKRetargeter_UEMannequinToMixamo)  
							 | 
						|||
| 
								 | 
							
								);
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## 判断骨骼结构是否符合要求
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								bool FMixamoSkeletonRetargeter::IsMixamoSkeleton(const USkeleton * Skeleton) const  
							 | 
						|||
| 
								 | 
							
								{  
							 | 
						|||
| 
								 | 
							
								    // We consider a Skeleton "coming from Mixamo" if it has at least X% of the expected bones.  
							 | 
						|||
| 
								 | 
							
								    const float MINIMUM_MATCHING_PERCENTAGE = .75f;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    // Convert the array of expected bone names (TODO: cache it...).  
							 | 
						|||
| 
								 | 
							
								    TArray<FName> BoneNames;  
							 | 
						|||
| 
								 | 
							
								    UE4MannequinToMixamo_BoneNamesMapping.GetDestination(BoneNames);  
							 | 
						|||
| 
								 | 
							
								    // Look for and count the known Mixamo bones (see comments on IndexLastCheckedMixamoBone and UEMannequinToMixamo_BonesMapping).  
							 | 
						|||
| 
								 | 
							
								    constexpr int32 NumBones = (IndexLastCheckedMixamoBone + 1) / 2;  
							 | 
						|||
| 
								 | 
							
								    BoneNames.SetNum(NumBones);  
							 | 
						|||
| 
								 | 
							
								    FSkeletonMatcher SkeletonMatcher(BoneNames, MINIMUM_MATCHING_PERCENTAGE);  
							 | 
						|||
| 
								 | 
							
								    return SkeletonMatcher.IsMatching(Skeleton);  
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								bool FSkeletonMatcher::IsMatching(const USkeleton* Skeleton) const  
							 | 
						|||
| 
								 | 
							
								{  
							 | 
						|||
| 
								 | 
							
								    // No Skeleton, No matching...  
							 | 
						|||
| 
								 | 
							
								    if (Skeleton == nullptr)  
							 | 
						|||
| 
								 | 
							
								    {       return false;  
							 | 
						|||
| 
								 | 
							
								    }  
							 | 
						|||
| 
								 | 
							
								    const int32 NumExpectedBones = BoneNames.Num();  
							 | 
						|||
| 
								 | 
							
								    int32 nMatchingBones = 0;  
							 | 
						|||
| 
								 | 
							
								    const FReferenceSkeleton & SkeletonRefSkeleton = Skeleton->GetReferenceSkeleton();  
							 | 
						|||
| 
								 | 
							
								    for (int32 i = 0; i < NumExpectedBones; ++i)  
							 | 
						|||
| 
								 | 
							
								    {       const int32 BoneIndex = SkeletonRefSkeleton.FindBoneIndex(BoneNames[i]);  
							 | 
						|||
| 
								 | 
							
								       if (BoneIndex != INDEX_NONE)  
							 | 
						|||
| 
								 | 
							
								       {          ++nMatchingBones;  
							 | 
						|||
| 
								 | 
							
								       }    }    const float MatchedPercentage = float(nMatchingBones) / float(NumExpectedBones);  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    return MatchedPercentage >= MinimumMatchingPerc;  
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								enum class ETargetSkeletonType  
							 | 
						|||
| 
								 | 
							
								{  
							 | 
						|||
| 
								 | 
							
								    ST_UNKNOWN = 0,  
							 | 
						|||
| 
								 | 
							
								    ST_UE4_MANNEQUIN,  
							 | 
						|||
| 
								 | 
							
								    ST_UE5_MANNEQUIN,  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    ST_SIZE  
							 | 
						|||
| 
								 | 
							
								};
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								static const char* const kUE4MannequinToMixamo_BoneNamesMapping[] = {  
							 | 
						|||
| 
								 | 
							
								    // UE Mannequin bone name     MIXAMO bone name  
							 | 
						|||
| 
								 | 
							
								    "root",                "root",  
							 | 
						|||
| 
								 | 
							
								    "pelvis",           "Hips",  
							 | 
						|||
| 
								 | 
							
								    "spine_01",          "Spine",  
							 | 
						|||
| 
								 | 
							
								    "spine_02",          "Spine1",  
							 | 
						|||
| 
								 | 
							
								    "spine_03",          "Spine2",  
							 | 
						|||
| 
								 | 
							
								    "neck_01",              "Neck",  
							 | 
						|||
| 
								 | 
							
								    "head",             "head",  
							 | 
						|||
| 
								 | 
							
								    "clavicle_l",        "LeftShoulder",  
							 | 
						|||
| 
								 | 
							
								    "upperarm_l",        "LeftArm",  
							 | 
						|||
| 
								 | 
							
								    "lowerarm_l",        "LeftForeArm",  
							 | 
						|||
| 
								 | 
							
								    "hand_l",           "LeftHand",  
							 | 
						|||
| 
								 | 
							
								    "clavicle_r",        "RightShoulder",  
							 | 
						|||
| 
								 | 
							
								    "upperarm_r",        "RightArm",  
							 | 
						|||
| 
								 | 
							
								    "lowerarm_r",        "RightForeArm",  
							 | 
						|||
| 
								 | 
							
								    "hand_r",           "RightHand",  
							 | 
						|||
| 
								 | 
							
								    "thigh_l",              "LeftUpLeg",  
							 | 
						|||
| 
								 | 
							
								    "calf_l",           "LeftLeg",  
							 | 
						|||
| 
								 | 
							
								    "foot_l",           "LeftFoot",  
							 | 
						|||
| 
								 | 
							
								    "ball_l",           "LeftToeBase",  
							 | 
						|||
| 
								 | 
							
								    "thigh_r",              "RightUpLeg",  
							 | 
						|||
| 
								 | 
							
								    "calf_r",           "RightLeg",  
							 | 
						|||
| 
								 | 
							
								    "foot_r",           "RightFoot",  
							 | 
						|||
| 
								 | 
							
								    "ball_r",           "RightToeBase",  
							 | 
						|||
| 
								 | 
							
								    // From here, ignored to determine if a skeleton is from Mixamo.  
							 | 
						|||
| 
								 | 
							
								    // From here, ignored to determine if a skeleton is from UE Mannequin.    "index_01_l",        "LeftHandIndex1",  
							 | 
						|||
| 
								 | 
							
								    "index_02_l",        "LeftHandIndex2",  
							 | 
						|||
| 
								 | 
							
								    "index_03_l",        "LeftHandIndex3",  
							 | 
						|||
| 
								 | 
							
								    "middle_01_l",           "LeftHandMiddle1",  
							 | 
						|||
| 
								 | 
							
								    "middle_02_l",           "LeftHandMiddle2",  
							 | 
						|||
| 
								 | 
							
								    "middle_03_l",           "LeftHandMiddle3",  
							 | 
						|||
| 
								 | 
							
								    "pinky_01_l",        "LeftHandPinky1",  
							 | 
						|||
| 
								 | 
							
								    "pinky_02_l",        "LeftHandPinky2",  
							 | 
						|||
| 
								 | 
							
								    "pinky_03_l",        "LeftHandPinky3",  
							 | 
						|||
| 
								 | 
							
								    "ring_01_l",         "LeftHandRing1",  
							 | 
						|||
| 
								 | 
							
								    "ring_02_l",         "LeftHandRing2",  
							 | 
						|||
| 
								 | 
							
								    "ring_03_l",         "LeftHandRing3",  
							 | 
						|||
| 
								 | 
							
								    "thumb_01_l",        "LeftHandThumb1",  
							 | 
						|||
| 
								 | 
							
								    "thumb_02_l",        "LeftHandThumb2",  
							 | 
						|||
| 
								 | 
							
								    "thumb_03_l",        "LeftHandThumb3",  
							 | 
						|||
| 
								 | 
							
								    "index_01_r",        "RightHandIndex1",  
							 | 
						|||
| 
								 | 
							
								    "index_02_r",        "RightHandIndex2",  
							 | 
						|||
| 
								 | 
							
								    "index_03_r",        "RightHandIndex3",  
							 | 
						|||
| 
								 | 
							
								    "middle_01_r",           "RightHandMiddle1",  
							 | 
						|||
| 
								 | 
							
								    "middle_02_r",           "RightHandMiddle2",  
							 | 
						|||
| 
								 | 
							
								    "middle_03_r",           "RightHandMiddle3",  
							 | 
						|||
| 
								 | 
							
								    "pinky_01_r",        "RightHandPinky1",  
							 | 
						|||
| 
								 | 
							
								    "pinky_02_r",        "RightHandPinky2",  
							 | 
						|||
| 
								 | 
							
								    "pinky_03_r",        "RightHandPinky3",  
							 | 
						|||
| 
								 | 
							
								    "ring_01_r",         "RightHandRing1",  
							 | 
						|||
| 
								 | 
							
								    "ring_02_r",         "RightHandRing2",  
							 | 
						|||
| 
								 | 
							
								    "ring_03_r",         "RightHandRing3",  
							 | 
						|||
| 
								 | 
							
								    "thumb_01_r",        "RightHandThumb1",  
							 | 
						|||
| 
								 | 
							
								    "thumb_02_r",        "RightHandThumb2",  
							 | 
						|||
| 
								 | 
							
								    "thumb_03_r",        "RightHandThumb3",  
							 | 
						|||
| 
								 | 
							
								    // Un-mapped bones (at the moment). Here for reference.  
							 | 
						|||
| 
								 | 
							
								    //"lowerarm_twist_01_l",   nullptr,    //"upperarm_twist_01_l",   nullptr,    //"lowerarm_twist_01_r",   nullptr,    //"upperarm_twist_01_r",   nullptr,    //"calf_twist_01_l",      nullptr,    //"thigh_twist_01_l",  nullptr,    //"calf_twist_01_r",      nullptr,    //"thigh_twist_01_r",  nullptr,    //"ik_foot_root",        nullptr,    //"ik_foot_l",       nullptr,    //"ik_foot_r",       nullptr,    //"ik_hand_root",        nullptr,    //"ik_hand_gun",         nullptr,    //"ik_hand_l",       nullptr,    //"ik_hand_r",       nullptr,};
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# EasyPose
							 | 
						|||
| 
								 | 
							
								主要的逻辑都是在MatchPose()中,作者还是编写几个辅助函数以及IKRigRetargetChainDetail结构体。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								定义srcRtgSkeleton、tgtRtgSkeleton之外还定义了orgTgtRtgSkeleton,主要是为了在最后结算最终的Offset。UE的重定向Pose数据使用分离的方式存储(Maya也是这样,但UE输出的FBX却不是),分为RefPose与RetargetPoseOffset。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								- 初始化数据
							 | 
						|||
| 
								 | 
							
								- 初始化IKRigRetargetChainDetail srcIkRigRtgChainDetail & IKRigRetargetChainDetail tgtIkRigRtgChainDetail结构体以用于之后的计算
							 | 
						|||
| 
								 | 
							
								- 遍历骨骼链
							 | 
						|||
| 
								 | 
							
									- 根据当前Source & Target ID计算当前长度的百分比,如果TargetID > SourceID,则**srcId++**,并且跳过当前循环。(主要是考虑到骨骼链内骨骼数量不对等的情况)
							 | 
						|||
| 
								 | 
							
									- 取得当前SourceBoneLocation以及SourceNextBoneLocation来计算Direction
							 | 
						|||
| 
								 | 
							
									- 重新生成tgtRtgSkeleton、orgTgtRtgSkeleton的重定向Pose(**WorldSpace**)
							 | 
						|||
| 
								 | 
							
									- **之后是核心MatchPose逻辑**
							 | 
						|||
| 
								 | 
							
									- 计算Direction与TargetBoneDirection的旋转偏移,转成**LocalSpace**之后应用到tgtRtgSkeleton的对应Target骨骼上。
							 | 
						|||
| 
								 | 
							
									- 在X 或者 Y轴对齐的情况下 计算Source ID与Target ID 对应骨骼的旋转Offset,转成**LocalSpace**之后应用到tgtRtgSkeleton的对应Target骨骼上。
							 | 
						|||
| 
								 | 
							
									-  计算Direction与TargetBoneDirection的旋转Offset,转成**LocalSpace**之后应用到tgtRtgSkeleton的对应Target骨骼上。(以上上步相同)
							 | 
						|||
| 
								 | 
							
									- 使用tgtRtgSkeleton、orgTgtRtgSkeleton计算最终的旋转Offset。
							 | 
						|||
| 
								 | 
							
									- 使用最终FinalyOffset 应用到 Target的IKRig 的 FIKRetargetPose。
							 | 
						|||
| 
								 | 
							
									- **tgtId++**
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								以下是完整的代码:
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								int32 UAutomationUtilityLibrary::MatchPose(UIKRetargeter *iKRetargeter) {  
							 | 
						|||
| 
								 | 
							
								    int totalRotationsCount = 0;  
							 | 
						|||
| 
								 | 
							
								    if(iKRetargeter == nullptr) return 0;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    auto Target = ERetargetSourceOrTarget::Target;  
							 | 
						|||
| 
								 | 
							
								    auto Source = ERetargetSourceOrTarget::Source;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    UIKRetargeterController *ikRtgCtrl = UIKRetargeterController::GetController(iKRetargeter);  
							 | 
						|||
| 
								 | 
							
								    if(ikRtgCtrl == nullptr) return 0;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    const UIKRigDefinition* SourceIKRig = iKRetargeter->GetSourceIKRig();  
							 | 
						|||
| 
								 | 
							
								    const UIKRigDefinition* TargetIKRig = iKRetargeter->GetTargetIKRig();  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    const TArray<FBoneChain>& srcRtgChains = SourceIKRig->GetRetargetChains();  
							 | 
						|||
| 
								 | 
							
								    const TArray<FBoneChain>& tgtRtgChains = TargetIKRig->GetRetargetChains();  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    const FName& srcRtgRootName = SourceIKRig->GetRetargetRoot();  
							 | 
						|||
| 
								 | 
							
								    const FName& tgtRtgRootName = TargetIKRig->GetRetargetRoot();  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    FRetargetSkeleton srcRtgSkeleton;  
							 | 
						|||
| 
								 | 
							
								    FRetargetSkeleton tgtRtgSkeleton, orgTgtRtgSkeleton;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    auto tgtRtgPoseName = iKRetargeter->GetCurrentRetargetPoseName(ERetargetSourceOrTarget::Target);  
							 | 
						|||
| 
								 | 
							
								    FIKRetargetPose const *tgtRtgPose = iKRetargeter->GetCurrentRetargetPose(ERetargetSourceOrTarget::Target);  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    srcRtgSkeleton.Initialize(  
							 | 
						|||
| 
								 | 
							
								        ikRtgCtrl->GetPreviewMesh(Source),  
							 | 
						|||
| 
								 | 
							
								        srcRtgChains,  
							 | 
						|||
| 
								 | 
							
								        iKRetargeter->GetCurrentRetargetPoseName(ERetargetSourceOrTarget::Source),  
							 | 
						|||
| 
								 | 
							
								        iKRetargeter->GetCurrentRetargetPose(ERetargetSourceOrTarget::Source),  
							 | 
						|||
| 
								 | 
							
								        srcRtgRootName);  
							 | 
						|||
| 
								 | 
							
								    tgtRtgSkeleton.Initialize(  
							 | 
						|||
| 
								 | 
							
								        ikRtgCtrl->GetPreviewMesh(Target),  
							 | 
						|||
| 
								 | 
							
								        tgtRtgChains,  
							 | 
						|||
| 
								 | 
							
								        tgtRtgPoseName,  
							 | 
						|||
| 
								 | 
							
								        iKRetargeter->GetCurrentRetargetPose(ERetargetSourceOrTarget::Target),  
							 | 
						|||
| 
								 | 
							
								        tgtRtgRootName);  
							 | 
						|||
| 
								 | 
							
								    orgTgtRtgSkeleton.Initialize(  
							 | 
						|||
| 
								 | 
							
								        ikRtgCtrl->GetPreviewMesh(Target),  
							 | 
						|||
| 
								 | 
							
								        tgtRtgChains,  
							 | 
						|||
| 
								 | 
							
								        tgtRtgPoseName,  
							 | 
						|||
| 
								 | 
							
								        iKRetargeter->GetCurrentRetargetPose(ERetargetSourceOrTarget::Target),  
							 | 
						|||
| 
								 | 
							
								        tgtRtgRootName);  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    UIKRigProcessor* SourceIKRigProcessor=NewObject<UIKRigProcessor>();  
							 | 
						|||
| 
								 | 
							
								    SourceIKRigProcessor->Initialize(SourceIKRig,SourceIKRig->GetPreviewMesh());  
							 | 
						|||
| 
								 | 
							
								    UIKRigProcessor* TargetIKRigProcessor=NewObject<UIKRigProcessor>();  
							 | 
						|||
| 
								 | 
							
								    TargetIKRigProcessor->Initialize(TargetIKRig,TargetIKRig->GetPreviewMesh());  
							 | 
						|||
| 
								 | 
							
								    const FIKRigSkeleton& srcRigSkeleton = SourceIKRigProcessor->GetSkeleton();  
							 | 
						|||
| 
								 | 
							
								    const FIKRigSkeleton& tgtRigSkeleton = TargetIKRigProcessor->GetSkeleton();  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								    for(TObjectPtr<URetargetChainSettings> chainSettings : iKRetargeter->GetAllChainSettings()) {  
							 | 
						|||
| 
								 | 
							
								        if(chainSettings == nullptr) continue;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								        const FBoneChain *srcBoneChain = SourceIKRig->GetRetargetChainByName(chainSettings->SourceChain);  
							 | 
						|||
| 
								 | 
							
								        const FBoneChain *tgtBoneChain = TargetIKRig->GetRetargetChainByName(chainSettings->TargetChain);  
							 | 
						|||
| 
								 | 
							
								        if(srcBoneChain == nullptr || tgtBoneChain == nullptr) continue;  
							 | 
						|||
| 
								 | 
							
								        auto chainRoot = FName(TEXT("Root"));  
							 | 
						|||
| 
								 | 
							
								        if(chainSettings->SourceChain == chainRoot || chainSettings->TargetChain == chainRoot) {  
							 | 
						|||
| 
								 | 
							
								            continue;  
							 | 
						|||
| 
								 | 
							
								        }  
							 | 
						|||
| 
								 | 
							
								        IKRigRetargetChainDetail srcIkRigRtgChainDetail{srcBoneChain, srcRigSkeleton, srcRtgSkeleton};  
							 | 
						|||
| 
								 | 
							
								        IKRigRetargetChainDetail tgtIkRigRtgChainDetail{tgtBoneChain, tgtRigSkeleton, tgtRtgSkeleton};  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								        if(srcIkRigRtgChainDetail.ikRigRetargetMap.IsEmpty() || tgtIkRigRtgChainDetail.ikRigRetargetMap.IsEmpty()) {  
							 | 
						|||
| 
								 | 
							
								            //UE_LOG(AutomationFunctionLibrary, Log, TEXT("Chain Empty: %s: %d, %s: %d"), *srcIkRigRtgChainDetail.boneChain->ChainName.ToString(), srcIkRigRtgChainDetail.ikRigRetargetMap.Num(), *tgtIkRigRtgChainDetail.boneChain->ChainName.ToString(), tgtIkRigRtgChainDetail.ikRigRetargetMap.Num());  
							 | 
						|||
| 
								 | 
							
								            continue;  
							 | 
						|||
| 
								 | 
							
								        }  
							 | 
						|||
| 
								 | 
							
								        auto srcWatermarkLoc = srcRtgSkeleton.RetargetGlobalPose[srcIkRigRtgChainDetail.ikRigRetargetMap[0].retargetId].GetTranslation();  
							 | 
						|||
| 
								 | 
							
								        int rotationsCount = 0;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								        for(int32 srcId=0,tgtId=0; srcId<srcIkRigRtgChainDetail.ikRigRetargetMap.Num()-1 && tgtId<tgtIkRigRtgChainDetail.ikRigRetargetMap.Num()-1;) {  
							 | 
						|||
| 
								 | 
							
								            auto [srcIkRigId, srcRtgId, srcLenToNext, srcTotalLenUntilCurrent] = srcIkRigRtgChainDetail.ikRigRetargetMap[srcId];  
							 | 
						|||
| 
								 | 
							
								            auto [tgtIkRigId, tgtRtgId, tgtLenToNext, tgtTotalLenUntilCurrent] = tgtIkRigRtgChainDetail.ikRigRetargetMap[tgtId];  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								            float srcTotalLenToNextPercent = (srcTotalLenUntilCurrent + srcLenToNext) / srcIkRigRtgChainDetail.totalLength;  
							 | 
						|||
| 
								 | 
							
								            float tgtTotalLenToNextPercent = (tgtTotalLenUntilCurrent + tgtLenToNext) / tgtIkRigRtgChainDetail.totalLength;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								            if(tgtTotalLenToNextPercent > srcTotalLenToNextPercent) {  
							 | 
						|||
| 
								 | 
							
								                srcId++;  
							 | 
						|||
| 
								 | 
							
								                continue;  
							 | 
						|||
| 
								 | 
							
								            } else {  
							 | 
						|||
| 
								 | 
							
								                auto tgtRtgBoneName = tgtRigSkeleton.BoneNames[tgtIkRigId];  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								                auto diffLenPercentToNext = 1 - ((srcTotalLenToNextPercent - tgtTotalLenToNextPercent) * srcIkRigRtgChainDetail.totalLength / srcLenToNext);  
							 | 
						|||
| 
								 | 
							
								                auto srcCurrentBoneLoc = srcRtgSkeleton.RetargetGlobalPose[srcIkRigRtgChainDetail.ikRigRetargetMap[srcId].retargetId].GetTranslation();  
							 | 
						|||
| 
								 | 
							
								                auto srcNextBoneLoc = srcRtgSkeleton.RetargetGlobalPose[srcIkRigRtgChainDetail.ikRigRetargetMap[srcId+1].retargetId].GetTranslation();  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								                auto nextPoint = (srcNextBoneLoc - srcCurrentBoneLoc) * (diffLenPercentToNext) + srcCurrentBoneLoc;  
							 | 
						|||
| 
								 | 
							
								                auto direction = (nextPoint - srcWatermarkLoc).GetSafeNormal();  
							 | 
						|||
| 
								 | 
							
								                srcWatermarkLoc = nextPoint;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								                //ResetRetargetPose  
							 | 
						|||
| 
								 | 
							
								                ikRtgCtrl->ResetRetargetPose(tgtRtgPoseName, TArray<FName>{tgtRtgBoneName}, Target);  
							 | 
						|||
| 
								 | 
							
								                tgtRtgSkeleton.GenerateRetargetPose(tgtRtgPoseName, tgtRtgPose, tgtRtgRootName);  
							 | 
						|||
| 
								 | 
							
								                orgTgtRtgSkeleton.GenerateRetargetPose(tgtRtgPoseName, tgtRtgPose, tgtRtgRootName);  
							 | 
						|||
| 
								 | 
							
								                //根据骨骼链中当前SourceBone与下一个SourceBone,计算出Direction(世界坐标)  
							 | 
						|||
| 
								 | 
							
								                //GetBoneDirectionOffset 调用GetBoneDirectionOffset()计算出旋转偏移,再转换成LocalSpace旋转,再设置给骨骼。  
							 | 
						|||
| 
								 | 
							
								                auto const &tgtGlobalTransform = tgtRtgSkeleton.RetargetGlobalPose[tgtRtgId];  
							 | 
						|||
| 
								 | 
							
								                auto rotOffset = GetBoneDirectionOffset(tgtRtgSkeleton, tgtIkRigRtgChainDetail.ikRigRetargetMap[tgtId].retargetId, tgtIkRigRtgChainDetail.ikRigRetargetMap[tgtId+1].retargetId, direction);  
							 | 
						|||
| 
								 | 
							
								                rotOffset = GetBoneSpaceOffset(rotOffset, tgtGlobalTransform);  
							 | 
						|||
| 
								 | 
							
								                //UE_LOG(AutomationFunctionLibrary, VeryVerbose, TEXT("Matching direction: %s, %s"), *tgtRtgBoneName.ToString(), *rotOffset.ToString());  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								                if(NotEmptyOrIdentity(rotOffset)) {  
							 | 
						|||
| 
								 | 
							
								                    //UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Matched direction: %s"), *tgtRtgBoneName.ToString());  
							 | 
						|||
| 
								 | 
							
								                    ApplyRotationOffset(rotOffset, tgtRtgId, tgtRtgSkeleton);  
							 | 
						|||
| 
								 | 
							
								                }  
							 | 
						|||
| 
								 | 
							
								                //在X 或者 Y轴对齐的情况下 计算Source与Target 对应骨骼的旋转偏差,在转化成LocalSpace后再应用  
							 | 
						|||
| 
								 | 
							
								                rotOffset = GetXYRotationOffsetIfAligned(tgtRtgSkeleton.RetargetGlobalPose[tgtRtgId].GetRotation(), srcRtgSkeleton.RetargetGlobalPose[srcRtgId].GetRotation());  
							 | 
						|||
| 
								 | 
							
								                rotOffset = GetBoneSpaceOffset(rotOffset, tgtGlobalTransform);  
							 | 
						|||
| 
								 | 
							
								                //UE_LOG(AutomationFunctionLibrary, VeryVerbose, TEXT("Matching XY: %s, %s"), *tgtRtgBoneName.ToString(), *rotOffset.ToString());  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								                if(NotEmptyOrIdentity(rotOffset)) {  
							 | 
						|||
| 
								 | 
							
								                    //UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Matched XY: %s"), *tgtRtgBoneName.ToString());  
							 | 
						|||
| 
								 | 
							
								                    ApplyRotationOffset(rotOffset, tgtRtgId, tgtRtgSkeleton);  
							 | 
						|||
| 
								 | 
							
								                }  
							 | 
						|||
| 
								 | 
							
								                //与第一步相同  
							 | 
						|||
| 
								 | 
							
								                rotOffset = GetBoneDirectionOffset(tgtRtgSkeleton, tgtIkRigRtgChainDetail.ikRigRetargetMap[tgtId].retargetId, tgtIkRigRtgChainDetail.ikRigRetargetMap[tgtId+1].retargetId, direction);  
							 | 
						|||
| 
								 | 
							
								                rotOffset = GetBoneSpaceOffset(rotOffset, tgtGlobalTransform);  
							 | 
						|||
| 
								 | 
							
								                //UE_LOG(AutomationFunctionLibrary, VeryVerbose, TEXT("Matching direction 2nd: %s, %s"), *tgtRtgBoneName.ToString(), *rotOffset.ToString());  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								                if(NotEmptyOrIdentity(rotOffset)) {  
							 | 
						|||
| 
								 | 
							
								                    //UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Matched direction 2nd: %s"), *tgtRtgBoneName.ToString());  
							 | 
						|||
| 
								 | 
							
								                    ApplyRotationOffset(rotOffset, tgtRtgId, tgtRtgSkeleton);  
							 | 
						|||
| 
								 | 
							
								                }  
							 | 
						|||
| 
								 | 
							
								                //计算最终的旋转offset  
							 | 
						|||
| 
								 | 
							
								                auto finalOffset = GetRotationDifference(orgTgtRtgSkeleton.RetargetLocalPose[tgtRtgId].GetRotation(), tgtRtgSkeleton.RetargetLocalPose[tgtRtgId].GetRotation());  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								                if(NotEmptyOrIdentity(finalOffset)) {  
							 | 
						|||
| 
								 | 
							
								                    rotationsCount++;  
							 | 
						|||
| 
								 | 
							
								                    ikRtgCtrl->SetRotationOffsetForRetargetPoseBone(tgtRtgBoneName, finalOffset, Target);  
							 | 
						|||
| 
								 | 
							
								                }                tgtId++;  
							 | 
						|||
| 
								 | 
							
								            }  
							 | 
						|||
| 
								 | 
							
								        }  
							 | 
						|||
| 
								 | 
							
								        auto [srcIkRigId, srcRtgId, srcLenToNext, srcTotalLenUntilCurrent] = srcIkRigRtgChainDetail.ikRigRetargetMap.Last();  
							 | 
						|||
| 
								 | 
							
								        auto [tgtIkRigId, tgtRtgId, tgtLenToNext, tgtTotalLenUntilCurrent] = tgtIkRigRtgChainDetail.ikRigRetargetMap.Last();  
							 | 
						|||
| 
								 | 
							
								        auto tgtRtgBoneName = tgtRigSkeleton.BoneNames[tgtIkRigId];  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								        auto rotOffset = GetRotationOffsetIfAligned(tgtRtgSkeleton.RetargetGlobalPose[tgtRtgId].GetRotation(), srcRtgSkeleton.RetargetGlobalPose[srcRtgId].GetRotation());  
							 | 
						|||
| 
								 | 
							
								        //UE_LOG(AutomationFunctionLibrary, VeryVerbose, TEXT("Matching rotation: %s, %s"), *tgtRtgBoneName.ToString(), *rotOffset.ToString());  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								        if(NotEmptyOrIdentity(rotOffset)) {  
							 | 
						|||
| 
								 | 
							
								            rotationsCount++;  
							 | 
						|||
| 
								 | 
							
								            //UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Matched rotation: %s"), *tgtRtgBoneName.ToString());  
							 | 
						|||
| 
								 | 
							
								            ikRtgCtrl->SetRotationOffsetForRetargetPoseBone(tgtRtgBoneName, ikRtgCtrl->GetRotationOffsetForRetargetPoseBone(tgtRtgBoneName, Target) * rotOffset, Target);  
							 | 
						|||
| 
								 | 
							
								        }        if(rotationsCount > 0) {  
							 | 
						|||
| 
								 | 
							
								            //UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Chain: %s - %s: %d rotations"), *chainSettings->SourceChain.ToString(), *chainSettings->TargetChain.ToString(), rotationsCount);  
							 | 
						|||
| 
								 | 
							
								            totalRotationsCount += rotationsCount;  
							 | 
						|||
| 
								 | 
							
								        }    }    /*  
							 | 
						|||
| 
								 | 
							
								    if(totalRotationsCount > 0) {        UE_LOG(AutomationFunctionLibrary, Log, TEXT("Asset \"%s\": made %d adjustments with pose: \"%s\"."),            *iKRetargeter->GetName(), totalRotationsCount,            *ikRtgCtrl->GetCurrentRetargetPoseName(Target).ToString());    } else {        UE_LOG(AutomationFunctionLibrary, Log, TEXT("Asset \"%s\": pose \"%s\" fit already!"),            *iKRetargeter->GetName(),            *ikRtgCtrl->GetCurrentRetargetPoseName(Target).ToString());    }    */    //     ikRtgCtrl->SetRotationOffsetForRetargetPoseBone(MeshRefSkeleton.GetBoneName(EditBoneIndex), Q, ERetargetSourceOrTarget::Target);  
							 | 
						|||
| 
								 | 
							
								    return totalRotationsCount;  
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 |