diff --git a/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs b/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs index 3c1f6aa2..984a6074 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using UnityEditor; using UnityEngine; @@ -122,14 +124,15 @@ namespace nadena.dev.modular_avatar.core.editor var avatarArmature = avatarHips.transform.parent; var outfitArmature = outfitHips.transform.parent; - if (outfitArmature.GetComponent() == null) + var merge = outfitArmature.GetComponent(); + if (merge == null) { - var merge = Undo.AddComponent(outfitArmature.gameObject); + merge = Undo.AddComponent(outfitArmature.gameObject); merge.mergeTarget = new AvatarObjectReference(); merge.mergeTarget.referencePath = RuntimeUtil.RelativePath(avatarRoot, avatarArmature.gameObject); merge.InferPrefixSuffix(); - HeuristicBoneMapper.RenameBonesByHeuristic(merge); } + HeuristicBoneMapper.RenameBonesByHeuristic(merge); if (outfitRoot != null && outfitRoot.GetComponent() == null @@ -277,8 +280,7 @@ namespace nadena.dev.modular_avatar.core.editor return true; } - private static bool FindBones(Object obj, out GameObject avatarRoot, out GameObject avatarHips, - out GameObject outfitHips) + private static bool FindBones(Object obj, out GameObject avatarRoot, out GameObject avatarHips, out GameObject outfitHips) { avatarHips = outfitHips = null; var outfitRoot = obj as GameObject; @@ -286,7 +288,7 @@ namespace nadena.dev.modular_avatar.core.editor ? RuntimeUtil.FindAvatarInParents(outfitRoot.transform)?.gameObject : null; if (outfitRoot == null || avatarRoot == null) return false; - + var avatarAnimator = avatarRoot.GetComponent(); if (avatarAnimator == null) { @@ -297,7 +299,27 @@ namespace nadena.dev.modular_avatar.core.editor return false; } - avatarHips = avatarAnimator.GetBoneTransform(HumanBodyBones.Hips)?.gameObject; + var avatarBoneMappings = GetAvatarBoneMappings(avatarAnimator); + if (!avatarBoneMappings.ContainsKey(HumanBodyBones.Hips)) + { + errorMessageGroups = new string[] + { + S("setup_outfit.err.no_hips") + }; + return false; + } + + // We do an explicit search for the hips bone rather than invoking the animator, as we want to control + // traversal order. + foreach (var maybeHips in avatarRoot.GetComponentsInChildren()) + { + if (maybeHips.name == avatarBoneMappings[HumanBodyBones.Hips] && !maybeHips.IsChildOf(outfitRoot.transform)) + { + avatarHips = maybeHips.gameObject; + break; + } + } + if (avatarHips == null) { errorMessageGroups = new string[] @@ -323,13 +345,13 @@ namespace nadena.dev.modular_avatar.core.editor { foreach (Transform tempHip in child) { - if (tempHip.name.Contains(avatarHips.name)) + if (tempHip.name.Contains(avatarBoneMappings[HumanBodyBones.Hips])) { outfitHips = tempHip.gameObject; } } } - hipsCandidates.Add(avatarHips.name); + hipsCandidates.Add(avatarBoneMappings[HumanBodyBones.Hips]); // If that doesn't work out, we'll check for heuristic bone mapper mappings. foreach (var hbm in HeuristicBoneMapper.BoneToNameMap[HumanBodyBones.Hips]) @@ -366,5 +388,17 @@ namespace nadena.dev.modular_avatar.core.editor return avatarHips != null && outfitHips != null; } + + private static ImmutableDictionary GetAvatarBoneMappings(Animator avatarAnimator) + { + var avatarHuman = avatarAnimator.avatar?.humanDescription.human ?? new HumanBone[0]; + return avatarHuman + .Where(hb => !string.IsNullOrEmpty(hb.boneName)) + .Select(hb => new KeyValuePair( + (HumanBodyBones) Enum.Parse(typeof(HumanBodyBones), hb.humanName.Replace(" ", "")), + hb.boneName + )) + .ToImmutableDictionary(); + } } } \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/HeuristicBoneMapper.cs b/Packages/nadena.dev.modular-avatar/Editor/HeuristicBoneMapper.cs index 966df029..b2bcdba3 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/HeuristicBoneMapper.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/HeuristicBoneMapper.cs @@ -10,6 +10,146 @@ namespace nadena.dev.modular_avatar.core.editor { private static readonly Regex PAT_END_NUMBER = new Regex(@"[_\.][0-9]+"); + private static readonly ImmutableDictionary> BoneChildren = + ImmutableDictionary>.Empty + .Add(HumanBodyBones.Hips, ImmutableList.Create( + HumanBodyBones.LeftUpperLeg, + HumanBodyBones.RightUpperLeg, + HumanBodyBones.Spine + )) + .Add(HumanBodyBones.LeftUpperLeg, ImmutableList.Create( + HumanBodyBones.LeftLowerLeg + )) + .Add(HumanBodyBones.RightUpperLeg, ImmutableList.Create( + HumanBodyBones.RightLowerLeg + )) + .Add(HumanBodyBones.LeftLowerLeg, ImmutableList.Create( + HumanBodyBones.LeftFoot + )) + .Add(HumanBodyBones.RightLowerLeg, ImmutableList.Create( + HumanBodyBones.RightFoot + )) + .Add(HumanBodyBones.LeftFoot, ImmutableList.Create( + HumanBodyBones.LeftToes + )) + .Add(HumanBodyBones.RightFoot, ImmutableList.Create( + HumanBodyBones.RightToes + )) + .Add(HumanBodyBones.Spine, ImmutableList.Create( + HumanBodyBones.Chest, + HumanBodyBones.UpperChest + )) + .Add(HumanBodyBones.Chest, ImmutableList.Create( + HumanBodyBones.Neck, + HumanBodyBones.LeftShoulder, + HumanBodyBones.RightShoulder + )) + .Add(HumanBodyBones.UpperChest, ImmutableList.Create( + HumanBodyBones.Neck, + HumanBodyBones.LeftShoulder, + HumanBodyBones.RightShoulder + )) + .Add(HumanBodyBones.Neck, ImmutableList.Create( + HumanBodyBones.Head + )) + .Add(HumanBodyBones.Head, ImmutableList.Create( + HumanBodyBones.LeftEye, + HumanBodyBones.RightEye, + HumanBodyBones.Jaw + )) + .Add(HumanBodyBones.LeftShoulder, ImmutableList.Create( + HumanBodyBones.LeftUpperArm + )) + .Add(HumanBodyBones.RightShoulder, ImmutableList.Create( + HumanBodyBones.RightUpperArm + )) + .Add(HumanBodyBones.LeftUpperArm, ImmutableList.Create( + HumanBodyBones.LeftLowerArm + )) + .Add(HumanBodyBones.RightUpperArm, ImmutableList.Create( + HumanBodyBones.RightLowerArm + )) + .Add(HumanBodyBones.LeftLowerArm, ImmutableList.Create( + HumanBodyBones.LeftHand + )) + .Add(HumanBodyBones.RightLowerArm, ImmutableList.Create( + HumanBodyBones.RightHand + )) + .Add(HumanBodyBones.LeftHand, ImmutableList.Create( + HumanBodyBones.LeftThumbProximal, + HumanBodyBones.LeftIndexProximal, + HumanBodyBones.LeftMiddleProximal, + HumanBodyBones.LeftRingProximal, + HumanBodyBones.LeftLittleProximal + )) + .Add(HumanBodyBones.RightHand, ImmutableList.Create( + HumanBodyBones.RightThumbProximal, + HumanBodyBones.RightIndexProximal, + HumanBodyBones.RightMiddleProximal, + HumanBodyBones.RightRingProximal, + HumanBodyBones.RightLittleProximal + )) + .Add(HumanBodyBones.LeftThumbProximal, ImmutableList.Create( + HumanBodyBones.LeftThumbIntermediate + )) + .Add(HumanBodyBones.RightThumbProximal, ImmutableList.Create( + HumanBodyBones.RightThumbIntermediate + )) + .Add(HumanBodyBones.LeftThumbIntermediate, ImmutableList.Create( + HumanBodyBones.LeftThumbDistal + )) + .Add(HumanBodyBones.RightThumbIntermediate, ImmutableList.Create( + HumanBodyBones.RightThumbDistal + )) + .Add(HumanBodyBones.LeftIndexProximal, ImmutableList.Create( + HumanBodyBones.LeftIndexIntermediate + )) + .Add(HumanBodyBones.RightIndexProximal, ImmutableList.Create( + HumanBodyBones.RightIndexIntermediate + )) + .Add(HumanBodyBones.LeftIndexIntermediate, ImmutableList.Create( + HumanBodyBones.LeftIndexDistal + )) + .Add(HumanBodyBones.RightIndexIntermediate, ImmutableList.Create( + HumanBodyBones.RightIndexDistal + )) + .Add(HumanBodyBones.LeftMiddleProximal, ImmutableList.Create( + HumanBodyBones.LeftMiddleIntermediate + )) + .Add(HumanBodyBones.RightMiddleProximal, ImmutableList.Create( + HumanBodyBones.RightMiddleIntermediate + )) + .Add(HumanBodyBones.LeftMiddleIntermediate, ImmutableList.Create( + HumanBodyBones.LeftMiddleDistal + )) + .Add(HumanBodyBones.RightMiddleIntermediate, ImmutableList.Create( + HumanBodyBones.RightMiddleDistal + )) + .Add(HumanBodyBones.LeftRingProximal, ImmutableList.Create( + HumanBodyBones.LeftRingIntermediate + )) + .Add(HumanBodyBones.RightRingProximal, ImmutableList.Create( + HumanBodyBones.RightRingIntermediate + )) + .Add(HumanBodyBones.LeftRingIntermediate, ImmutableList.Create( + HumanBodyBones.LeftRingDistal + )) + .Add(HumanBodyBones.RightRingIntermediate, ImmutableList.Create( + HumanBodyBones.RightRingDistal + )) + .Add(HumanBodyBones.LeftLittleProximal, ImmutableList.Create( + HumanBodyBones.LeftLittleIntermediate + )) + .Add(HumanBodyBones.RightLittleProximal, ImmutableList.Create( + HumanBodyBones.RightLittleIntermediate + )) + .Add(HumanBodyBones.LeftLittleIntermediate, ImmutableList.Create( + HumanBodyBones.LeftLittleDistal + )) + .Add(HumanBodyBones.RightLittleIntermediate, ImmutableList.Create( + HumanBodyBones.RightLittleDistal + )); + // This list is originally from https://github.com/HhotateA/AvatarModifyTools/blob/d8ae75fed8577707253d6b63a64d6053eebbe78b/Assets/HhotateA/AvatarModifyTool/Editor/EnvironmentVariable.cs#L81-L139 // Copyright (c) 2021 @HhotateA_xR // Licensed under the MIT License @@ -359,15 +499,31 @@ namespace nadena.dev.modular_avatar.core.editor var target = config.mergeTarget.Get(RuntimeUtil.FindAvatarInParents(config.transform)); if (target == null) return; + bool changedSuffix = false; + var newSuffix = config.suffix; + if (config.prefix == "" && config.suffix == "") + { + newSuffix = ".1"; + changedSuffix = true; + } + Traverse(config.transform, target.transform); + config.suffix = newSuffix; + + if (changedSuffix) + { + Undo.RecordObject(config, "Applying heuristic mapping"); + PrefabUtility.RecordPrefabInstancePropertyModifications(config); + } + void Traverse(Transform src, Transform dst) { var mappings = AssignBoneMappings(config, src.gameObject, dst.gameObject); foreach (var pair in mappings) { - var newName = config.prefix + pair.Value.gameObject.name + config.suffix; + var newName = config.prefix + pair.Value.gameObject.name + newSuffix; var srcGameObj = pair.Key.gameObject; var oldName = srcGameObj.name;