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;
|
||
}
|
||
```
|