Add a context menu setup option for outfits

This commit is contained in:
bd_ 2022-10-04 19:43:46 -07:00 committed by bd_
parent c40647e433
commit 76d2adefd3
4 changed files with 116 additions and 27 deletions

View File

@ -0,0 +1,80 @@
using UnityEditor;
using UnityEngine;
namespace net.fushizen.modular_avatar.core.editor
{
public class EasySetupOutfit
{
private const int PRIORITY = 49;
[MenuItem("GameObject/[ModularAvatar] Setup Outfit", false, PRIORITY)]
static void SetupOutfit(MenuCommand cmd)
{
if (!FindBones(cmd.context,
out var avatarRoot, out var avatarHips, out var outfitHips)
) return;
var avatarArmature = avatarHips.transform.parent;
var outfitArmature = outfitHips.transform.parent;
var merge = Undo.AddComponent<ModularAvatarMergeArmature>(outfitArmature.gameObject);
merge.mergeTarget.referencePath = RuntimeUtil.RelativePath(avatarRoot, avatarArmature.gameObject);
merge.InferPrefixSuffix();
}
[MenuItem("GameObject/[ModularAvatar] Setup Outfit", true, PRIORITY)]
static bool ValidateSetupOutfit()
{
foreach (var obj in Selection.objects)
{
if (!FindBones(obj, out var _, out var _, out var outfitHips)
|| outfitHips.transform.parent.GetComponent<ModularAvatarMergeArmature>() != null)
{
return false;
}
}
return true;
}
private static bool FindBones(Object obj, out GameObject avatarRoot, out GameObject avatarHips,
out GameObject outfitHips)
{
avatarHips = outfitHips = null;
var outfitRoot = obj as GameObject;
avatarRoot = outfitRoot != null
? RuntimeUtil.FindAvatarInParents(outfitRoot.transform)?.gameObject
: null;
if (outfitRoot == null || avatarRoot == null) return false;
var avatarAnimator = avatarRoot.GetComponent<Animator>();
if (avatarAnimator == null) return false;
avatarHips = avatarAnimator.GetBoneTransform(HumanBodyBones.Hips)?.gameObject;
if (avatarHips == null) return false;
var outfitAnimator = outfitRoot.GetComponent<Animator>();
if (outfitAnimator != null)
{
outfitHips = outfitAnimator.GetBoneTransform(HumanBodyBones.Hips)?.gameObject;
}
if (outfitHips == null)
{
// Heuristic search - usually there'll be root -> Armature -> (single child) Hips
foreach (Transform child in outfitRoot.transform)
{
if (child.childCount == 1)
{
if (child.GetChild(0).name.Contains(avatarHips.name))
{
outfitHips = child.GetChild(0).gameObject;
}
}
}
}
return avatarHips != null && outfitHips != null;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ce0a55eae40b424084cb91f80f6276c2
timeCreated: 1664935971

View File

@ -22,33 +22,7 @@ namespace net.fushizen.modular_avatar.core.editor
&& string.IsNullOrEmpty(target.prefix)
&& string.IsNullOrEmpty(target.suffix))
{
// We only infer if targeting the armature (below the Hips bone)
var rootAnimator = RuntimeUtil.FindAvatarInParents(target.transform)?.GetComponent<Animator>();
if (rootAnimator == null) return;
var hips = rootAnimator.GetBoneTransform(HumanBodyBones.Hips);
if (hips == null || hips.transform.parent != target.mergeTargetObject.transform) return;
// We also require that the attached object has exactly one child (presumably the hips)
if (target.transform.childCount != 1) return;
// Infer the prefix and suffix by comparing the names of the mergeTargetObject's hips with the child of the
// GameObject we're attached to.
var baseName = hips.name;
var mergeName = target.transform.GetChild(0).name;
var prefixLength = mergeName.IndexOf(baseName, StringComparison.InvariantCulture);
if (prefixLength < 0) return;
var suffixLength = mergeName.Length - prefixLength - baseName.Length;
target.prefix = mergeName.Substring(0, prefixLength);
target.suffix = mergeName.Substring(mergeName.Length - suffixLength);
if (!string.IsNullOrEmpty(target.prefix) || !string.IsNullOrEmpty(target.suffix))
{
RuntimeUtil.MarkDirty(target);
}
target.InferPrefixSuffix();
}
}
}

View File

@ -22,6 +22,7 @@
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
@ -186,5 +187,36 @@ namespace net.fushizen.modular_avatar.core
return correspondingParent.Find(prefix + xform.name + suffix);
}
public void InferPrefixSuffix()
{
// We only infer if targeting the armature (below the Hips bone)
var rootAnimator = RuntimeUtil.FindAvatarInParents(transform)?.GetComponent<Animator>();
if (rootAnimator == null) return;
var hips = rootAnimator.GetBoneTransform(HumanBodyBones.Hips);
if (hips == null || hips.transform.parent != mergeTargetObject.transform) return;
// We also require that the attached object has exactly one child (presumably the hips)
if (transform.childCount != 1) return;
// Infer the prefix and suffix by comparing the names of the mergeTargetObject's hips with the child of the
// GameObject we're attached to.
var baseName = hips.name;
var mergeName = transform.GetChild(0).name;
var prefixLength = mergeName.IndexOf(baseName, StringComparison.InvariantCulture);
if (prefixLength < 0) return;
var suffixLength = mergeName.Length - prefixLength - baseName.Length;
prefix = mergeName.Substring(0, prefixLength);
suffix = mergeName.Substring(mergeName.Length - suffixLength);
if (!string.IsNullOrEmpty(prefix) || !string.IsNullOrEmpty(suffix))
{
RuntimeUtil.MarkDirty(this);
}
}
}
}