feat: support using Humanoid Rig on RenameBonesByHeuristic

This commit is contained in:
Sayamame-beans 2024-09-17 00:50:02 +09:00
parent 9bbec4c86b
commit 97d8261557
3 changed files with 92 additions and 16 deletions

View File

@ -306,7 +306,9 @@ namespace nadena.dev.modular_avatar.core.editor
GameObject src, GameObject src,
GameObject newParent, GameObject newParent,
List<Transform> skipped = null, List<Transform> skipped = null,
HashSet<Transform> unassigned = null HashSet<Transform> unassigned = null,
Animator avatarAnimator = null,
Dictionary<Transform, HumanBodyBones> humanoidBones = null
) )
{ {
Dictionary<Transform, Transform> mappings = new Dictionary<Transform, Transform>(); Dictionary<Transform, Transform> mappings = new Dictionary<Transform, Transform>();
@ -355,21 +357,65 @@ namespace nadena.dev.modular_avatar.core.editor
var childName = child.gameObject.name; var childName = child.gameObject.name;
var targetObjectName = childName.Substring(config.prefix.Length, var targetObjectName = childName.Substring(config.prefix.Length,
childName.Length - config.prefix.Length - config.suffix.Length); childName.Length - config.prefix.Length - config.suffix.Length);
List<HumanBodyBones> bodyBones = null;
var isMapped = false;
if (!NameToBoneMap.TryGetValue( if (humanoidBones != null && humanoidBones.TryGetValue(child, out var humanoidBone))
NormalizeName(targetObjectName), out var bodyBones)) {
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<HumanBodyBones> { humanoidBone };
}
} else {
bodyBones = new List<HumanBodyBones>() { humanoidBone };
}
}
if (!isMapped && bodyBones == null && !NameToBoneMap.TryGetValue(
NormalizeName(targetObjectName), out bodyBones))
{ {
continue; 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; if (avatarAnimator != null)
unassigned.Remove(targetObject); {
lcNameToXform.Remove(otherName.ToLowerInvariant()); var avatarBone = avatarAnimator.GetBoneTransform(bodyBone);
break; 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; return mappings;
} }
internal static void RenameBonesByHeuristic(ModularAvatarMergeArmature config, List<Transform> skipped = null) internal static void RenameBonesByHeuristic(ModularAvatarMergeArmature config, List<Transform> skipped = null, Dictionary<Transform, HumanBodyBones> humanoidBones = null, Animator avatarAnimator = null)
{ {
var target = config.mergeTarget.Get(RuntimeUtil.FindAvatarTransformInParents(config.transform)); var target = config.mergeTarget.Get(RuntimeUtil.FindAvatarTransformInParents(config.transform));
if (target == null) return; if (target == null) return;
@ -399,7 +445,7 @@ namespace nadena.dev.modular_avatar.core.editor
void Traverse(Transform src, Transform dst) 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) foreach (var pair in mappings)
{ {

View File

@ -114,7 +114,9 @@ namespace nadena.dev.modular_avatar.core.editor
{ {
if (GUILayout.Button(G("merge_armature.adjust_names"))) 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<Animator>() : null;
HeuristicBoneMapper.RenameBonesByHeuristic(target, avatarAnimator: avatarAnimator);
} }
} }

View File

@ -8,6 +8,7 @@ using UnityEditor;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
using static nadena.dev.modular_avatar.core.editor.Localization; using static nadena.dev.modular_avatar.core.editor.Localization;
using System;
#endregion #endregion
@ -156,8 +157,36 @@ namespace nadena.dev.modular_avatar.core.editor
merge.LockMode = ArmatureLockMode.BaseToMerge; merge.LockMode = ArmatureLockMode.BaseToMerge;
merge.InferPrefixSuffix(); merge.InferPrefixSuffix();
var outfitAnimator = outfitRoot.GetComponent<Animator>();
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<Transform, HumanBodyBones> humanoidBones = null;
if (outfitAnimator != null)
{
humanoidBones = new Dictionary<Transform, HumanBodyBones>();
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<Animator>();
List<Transform> subRoots = new List<Transform>(); List<Transform> subRoots = new List<Transform>();
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 // If the outfit has an UpperChest bone but the avatar doesn't, add an additional MergeArmature to
// help with this // help with this
@ -187,7 +216,6 @@ namespace nadena.dev.modular_avatar.core.editor
outfitArmature.name += ".1"; outfitArmature.name += ".1";
// Also make sure to refresh the avatar's animator humanoid bone cache. // Also make sure to refresh the avatar's animator humanoid bone cache.
var avatarAnimator = avatarRoot.GetComponent<Animator>();
var humanDescription = avatarAnimator.avatar; var humanDescription = avatarAnimator.avatar;
avatarAnimator.avatar = null; avatarAnimator.avatar = null;
// ReSharper disable once Unity.InefficientPropertyAccess // ReSharper disable once Unity.InefficientPropertyAccess