diff --git a/Editor/HeuristicBoneMapper.cs b/Editor/HeuristicBoneMapper.cs index 5f5b1044..d3b36659 100644 --- a/Editor/HeuristicBoneMapper.cs +++ b/Editor/HeuristicBoneMapper.cs @@ -306,7 +306,9 @@ namespace nadena.dev.modular_avatar.core.editor GameObject src, GameObject newParent, List skipped = null, - HashSet unassigned = null + HashSet unassigned = null, + Animator avatarAnimator = null, + Dictionary humanoidBones = null ) { Dictionary mappings = new Dictionary(); @@ -355,21 +357,65 @@ namespace nadena.dev.modular_avatar.core.editor var childName = child.gameObject.name; var targetObjectName = childName.Substring(config.prefix.Length, childName.Length - config.prefix.Length - config.suffix.Length); - - if (!NameToBoneMap.TryGetValue( - NormalizeName(targetObjectName), out var bodyBones)) + List bodyBones = null; + var isMapped = false; + + if (humanoidBones != null && humanoidBones.TryGetValue(child, out var humanoidBone)) + { + if (avatarAnimator != null) + { + var avatarBone = avatarAnimator.GetBoneTransform(humanoidBone); + if (avatarBone != null) + { + mappings[child] = avatarBone; + unassigned.Remove(avatarBone); + lcNameToXform.Remove(NormalizeName(avatarBone.gameObject.name)); + isMapped = true; + } else { + bodyBones = new List { humanoidBone }; + } + } else { + bodyBones = new List() { humanoidBone }; + } + } + + if (!isMapped && bodyBones == null && !NameToBoneMap.TryGetValue( + NormalizeName(targetObjectName), out bodyBones)) { continue; } - foreach (var otherName in bodyBones.SelectMany(bone => BoneToNameMap[bone])) + if (!isMapped) { - if (lcNameToXform.TryGetValue(otherName, out var targetObject)) + foreach (var bodyBone in bodyBones) { - mappings[child] = targetObject; - unassigned.Remove(targetObject); - lcNameToXform.Remove(otherName.ToLowerInvariant()); - break; + if (avatarAnimator != null) + { + var avatarBone = avatarAnimator.GetBoneTransform(bodyBone); + if (avatarBone != null && unassigned.Contains(avatarBone)) + { + mappings[child] = avatarBone; + unassigned.Remove(avatarBone); + lcNameToXform.Remove(NormalizeName(avatarBone.gameObject.name)); + isMapped = true; + break; + } + } + } + } + + if (!isMapped) + { + foreach (var otherName in bodyBones.SelectMany(bone => BoneToNameMap[bone])) + { + if (lcNameToXform.TryGetValue(otherName, out var targetObject)) + { + mappings[child] = targetObject; + unassigned.Remove(targetObject); + lcNameToXform.Remove(otherName.ToLowerInvariant()); + isMapped = true; + break; + } } } @@ -388,7 +434,7 @@ namespace nadena.dev.modular_avatar.core.editor return mappings; } - internal static void RenameBonesByHeuristic(ModularAvatarMergeArmature config, List skipped = null) + internal static void RenameBonesByHeuristic(ModularAvatarMergeArmature config, List skipped = null, Dictionary humanoidBones = null, Animator avatarAnimator = null) { var target = config.mergeTarget.Get(RuntimeUtil.FindAvatarTransformInParents(config.transform)); if (target == null) return; @@ -399,7 +445,7 @@ namespace nadena.dev.modular_avatar.core.editor void Traverse(Transform src, Transform dst) { - var mappings = AssignBoneMappings(config, src.gameObject, dst.gameObject, skipped: skipped); + var mappings = AssignBoneMappings(config, src.gameObject, dst.gameObject, skipped: skipped, humanoidBones: humanoidBones, avatarAnimator: avatarAnimator); foreach (var pair in mappings) { diff --git a/Editor/Inspector/MergeArmatureEditor.cs b/Editor/Inspector/MergeArmatureEditor.cs index af1ab1c9..3aad6177 100644 --- a/Editor/Inspector/MergeArmatureEditor.cs +++ b/Editor/Inspector/MergeArmatureEditor.cs @@ -114,7 +114,9 @@ namespace nadena.dev.modular_avatar.core.editor { if (GUILayout.Button(G("merge_armature.adjust_names"))) { - HeuristicBoneMapper.RenameBonesByHeuristic(target); + var avatarRoot = RuntimeUtil.FindAvatarTransformInParents(target.mergeTarget.Get(target).transform); + var avatarAnimator = avatarRoot != null ? avatarRoot.GetComponent() : null; + HeuristicBoneMapper.RenameBonesByHeuristic(target, avatarAnimator: avatarAnimator); } } diff --git a/Editor/SetupOutfit.cs b/Editor/SetupOutfit.cs index f6d28c53..f8e4aab6 100644 --- a/Editor/SetupOutfit.cs +++ b/Editor/SetupOutfit.cs @@ -8,6 +8,7 @@ using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; using static nadena.dev.modular_avatar.core.editor.Localization; +using System; #endregion @@ -156,8 +157,36 @@ namespace nadena.dev.modular_avatar.core.editor merge.LockMode = ArmatureLockMode.BaseToMerge; merge.InferPrefixSuffix(); + var outfitAnimator = outfitRoot.GetComponent(); + if (outfitAnimator != null) + { + var hipsCheck = outfitAnimator.isHuman ? outfitAnimator.GetBoneTransform(HumanBodyBones.Hips) : null; + if (hipsCheck != null && hipsCheck.parent == outfitRoot.transform) + { + // Sometimes broken rigs can have the hips as a direct child of the root, instead of having + // an intermediate Armature object. We do not currently support this kind of rig, and so we'll + // assume the outfit's humanoid rig is broken and move on to heuristic matching. + outfitAnimator = null; + } else if (hipsCheck == null) { + outfitAnimator = null; + } + } + + Dictionary humanoidBones = null; + if (outfitAnimator != null) + { + humanoidBones = new Dictionary(); + foreach (HumanBodyBones boneIndex in Enum.GetValues(typeof(HumanBodyBones))) + { + var bone = boneIndex != HumanBodyBones.LastBone ? outfitAnimator.GetBoneTransform(boneIndex) : null; + if (bone == null) continue; + humanoidBones[bone] = boneIndex; + } + } + + var avatarAnimator = avatarRoot.GetComponent(); List subRoots = new List(); - HeuristicBoneMapper.RenameBonesByHeuristic(merge, skipped: subRoots); + HeuristicBoneMapper.RenameBonesByHeuristic(merge, skipped: subRoots, humanoidBones: humanoidBones, avatarAnimator: avatarAnimator); // If the outfit has an UpperChest bone but the avatar doesn't, add an additional MergeArmature to // help with this @@ -187,7 +216,6 @@ namespace nadena.dev.modular_avatar.core.editor outfitArmature.name += ".1"; // Also make sure to refresh the avatar's animator humanoid bone cache. - var avatarAnimator = avatarRoot.GetComponent(); var humanDescription = avatarAnimator.avatar; avatarAnimator.avatar = null; // ReSharper disable once Unity.InefficientPropertyAccess @@ -542,4 +570,4 @@ namespace nadena.dev.modular_avatar.core.editor return avatarHips != null && outfitHips != null; } } -} \ No newline at end of file +}