mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-28 10:15:06 +08:00
WIP: VPM bone mapping support
This commit is contained in:
parent
54d4e974db
commit
4aa46d2277
@ -138,7 +138,28 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
merge.mergeTarget.referencePath = RuntimeUtil.RelativePath(avatarRoot, avatarArmature.gameObject);
|
||||
merge.LockMode = ArmatureLockMode.BaseToMerge;
|
||||
merge.InferPrefixSuffix();
|
||||
HeuristicBoneMapper.RenameBonesByHeuristic(merge);
|
||||
|
||||
List<Transform> subRoots = new List<Transform>();
|
||||
HeuristicBoneMapper.RenameBonesByHeuristic(merge, skipped: subRoots);
|
||||
Debug.Log("Skipped: " + subRoots.Count);
|
||||
|
||||
// If the outfit has an UpperChest bone but the avatar doesn't, add an additional MergeArmature to
|
||||
// help with this
|
||||
foreach (var subRoot in subRoots)
|
||||
{
|
||||
Debug.Log("$$$ Add subconfig to " + RuntimeUtil.RelativePath(avatarRoot, subRoot.gameObject));
|
||||
var subConfig = Undo.AddComponent<ModularAvatarMergeArmature>(subRoot.gameObject);
|
||||
var parentConfig = subConfig.transform.parent.GetComponentInParent<ModularAvatarMergeArmature>();
|
||||
var parentMapping = parentConfig.MapBone(parentConfig.transform);
|
||||
|
||||
subConfig.mergeTarget = new AvatarObjectReference();
|
||||
subConfig.mergeTarget.referencePath =
|
||||
RuntimeUtil.RelativePath(avatarRoot, parentMapping.gameObject);
|
||||
subConfig.LockMode = ArmatureLockMode.BaseToMerge;
|
||||
subConfig.prefix = merge.prefix;
|
||||
subConfig.suffix = merge.suffix;
|
||||
subConfig.mangleNames = false;
|
||||
}
|
||||
|
||||
var avatarRootMatchingArmature = avatarRoot.transform.Find(outfitArmature.gameObject.name);
|
||||
if (merge.prefix == "" && merge.suffix == "" && avatarRootMatchingArmature != null)
|
||||
|
@ -225,16 +225,15 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
},
|
||||
new[] {"UpperChest", "UChest"},
|
||||
};
|
||||
|
||||
internal static readonly Regex Regex_VRM_Bone = new Regex(@"^([LRC])_(.*)$");
|
||||
|
||||
internal static string NormalizeName(string name)
|
||||
{
|
||||
name = name.ToLowerInvariant()
|
||||
.Replace("_", "")
|
||||
.Replace(".", "")
|
||||
.Replace(" ", "");
|
||||
name = Regex.Replace(name, "[0-9]", "");
|
||||
name = name.ToLowerInvariant();
|
||||
name = Regex.Replace(name, "[0-9 ._]", "");
|
||||
|
||||
return PAT_END_NUMBER.Replace(name, "");
|
||||
return name;
|
||||
}
|
||||
|
||||
internal static readonly ImmutableDictionary<string, List<HumanBodyBones>> NameToBoneMap;
|
||||
@ -259,6 +258,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
altName = match.Groups[1] + "." + altName;
|
||||
RegisterNameForBone(NormalizeName(altName), bone);
|
||||
}
|
||||
else
|
||||
{
|
||||
// VRM pattern: J_Bip_C_[non-sided bone, e.g. hips]
|
||||
var altName = "C." + name;
|
||||
RegisterNameForBone(NormalizeName(altName), bone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,16 +300,21 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
internal static Dictionary<Transform, Transform> AssignBoneMappings(
|
||||
ModularAvatarMergeArmature config,
|
||||
GameObject src,
|
||||
GameObject newParent
|
||||
GameObject newParent,
|
||||
List<Transform> skipped = null,
|
||||
HashSet<Transform> unassigned = null
|
||||
)
|
||||
{
|
||||
HashSet<Transform> unassigned = new HashSet<Transform>();
|
||||
Dictionary<Transform, Transform> mappings = new Dictionary<Transform, Transform>();
|
||||
List<Transform> heuristicAssignmentPass = new List<Transform>();
|
||||
|
||||
foreach (Transform child in newParent.transform)
|
||||
if (unassigned == null)
|
||||
{
|
||||
unassigned.Add(child);
|
||||
unassigned = new HashSet<Transform>();
|
||||
foreach (Transform child in newParent.transform)
|
||||
{
|
||||
unassigned.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Transform child in src.transform)
|
||||
@ -339,9 +349,9 @@ 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.ToLowerInvariant()), out var bodyBones))
|
||||
NormalizeName(targetObjectName), out var bodyBones))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -356,21 +366,38 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mappings.ContainsKey(child))
|
||||
{
|
||||
Debug.Log("Unassigned: " + childName + " bodyBones=" + string.Join(", ", bodyBones) + " skipped=" + (skipped != null ? string.Join(", ", skipped.Select(x => x.name)) : "null"));
|
||||
}
|
||||
if (!mappings.ContainsKey(child) && bodyBones.Contains(HumanBodyBones.UpperChest) && skipped != null)
|
||||
{
|
||||
// Avatars are often missing UpperChest bones, try skipping over this...
|
||||
skipped.Add(child);
|
||||
|
||||
foreach (var kvp in AssignBoneMappings(config, child.gameObject, newParent, skipped, unassigned))
|
||||
{
|
||||
mappings.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mappings;
|
||||
}
|
||||
|
||||
internal static void RenameBonesByHeuristic(ModularAvatarMergeArmature config)
|
||||
internal static void RenameBonesByHeuristic(ModularAvatarMergeArmature config, List<Transform> skipped = null)
|
||||
{
|
||||
var target = config.mergeTarget.Get(RuntimeUtil.FindAvatarTransformInParents(config.transform));
|
||||
if (target == null) return;
|
||||
|
||||
if (skipped == null) skipped = new List<Transform>();
|
||||
|
||||
Traverse(config.transform, target.transform);
|
||||
|
||||
|
||||
void Traverse(Transform src, Transform dst)
|
||||
{
|
||||
var mappings = AssignBoneMappings(config, src.gameObject, dst.gameObject);
|
||||
var mappings = AssignBoneMappings(config, src.gameObject, dst.gameObject, skipped: skipped);
|
||||
|
||||
foreach (var pair in mappings)
|
||||
{
|
||||
|
@ -160,7 +160,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
private void ForcePositionToBaseAvatar()
|
||||
{
|
||||
var mama = (ModularAvatarMergeArmature) target;
|
||||
var mama = (ModularAvatarMergeArmature)target;
|
||||
|
||||
ForcePositionToBaseAvatar(mama);
|
||||
}
|
||||
|
||||
private void ForcePositionToBaseAvatar(ModularAvatarMergeArmature mama, bool suppressRootScale = false) {
|
||||
var mergeTarget = mama.mergeTarget.Get(mama);
|
||||
var xform_to_bone = new Dictionary<Transform, HumanBodyBones>();
|
||||
var bone_to_xform = new Dictionary<HumanBodyBones, Transform>();
|
||||
@ -183,7 +188,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
|
||||
if (posReset_heuristicRootScale)
|
||||
if (posReset_heuristicRootScale && !suppressRootScale)
|
||||
{
|
||||
AdjustRootScale();
|
||||
}
|
||||
@ -219,14 +224,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
void Walk(Transform t_merge, Transform t_target)
|
||||
{
|
||||
Undo.RecordObject(t_merge, "Merge Armature: Force outfit position");
|
||||
|
||||
Debug.Log("=== Processing: " + t_merge.gameObject.name);
|
||||
|
||||
if (!t_merge.IsChildOf(mama.transform))
|
||||
{
|
||||
throw new ArgumentException("t_merge not a child of mama.transform");
|
||||
}
|
||||
|
||||
|
||||
Debug.Log("Merge: " + t_merge.gameObject.name + " => " + t_target.gameObject.name);
|
||||
|
||||
t_merge.position = t_target.position;
|
||||
if (posReset_adjustScale)
|
||||
{
|
||||
@ -241,11 +241,24 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
t_merge.localRotation = t_target.localRotation;
|
||||
}
|
||||
|
||||
foreach (Transform t_child in t_merge)
|
||||
Queue<Transform> traversalQueue = new Queue<Transform>();
|
||||
traversalQueue.Enqueue(t_merge);
|
||||
|
||||
while (traversalQueue.Count > 0)
|
||||
{
|
||||
if (TryMatchChildBone(t_target, t_child, out var t_target_child))
|
||||
foreach (Transform t_child in traversalQueue.Dequeue())
|
||||
{
|
||||
Walk(t_child, t_target_child);
|
||||
var mama_child = t_child.GetComponent<ModularAvatarMergeArmature>();
|
||||
if (mama_child != null)
|
||||
{
|
||||
traversalQueue.Enqueue(t_child);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryMatchChildBone(t_target, t_child, out var t_target_child))
|
||||
{
|
||||
Walk(t_child, t_target_child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,8 +121,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
foreach (var config in mergeArmatures)
|
||||
{
|
||||
// TODO - assert that we're not nesting merge armatures?
|
||||
|
||||
var target = config.mergeTargetObject;
|
||||
if (target == null)
|
||||
{
|
||||
@ -149,6 +147,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
TopoLoop(next);
|
||||
}
|
||||
|
||||
foreach (var next in mergeArmatures)
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(next);
|
||||
}
|
||||
|
||||
void TopoLoop(ModularAvatarMergeArmature config)
|
||||
{
|
||||
if (visited.Contains(config)) return;
|
||||
@ -196,7 +199,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
PruneDuplicatePhysBones();
|
||||
#endif
|
||||
UnityEngine.Object.DestroyImmediate(config);
|
||||
});
|
||||
}
|
||||
|
||||
@ -360,6 +362,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
foreach (Transform child in children)
|
||||
{
|
||||
if (child.GetComponent <ModularAvatarMergeArmature>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var childGameObject = child.gameObject;
|
||||
var childName = childGameObject.name;
|
||||
GameObject childNewParent = mergedSrcBone;
|
||||
|
@ -26,6 +26,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using nadena.dev.modular_avatar.core.armature_lock;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Analytics;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
@ -59,6 +60,27 @@ namespace nadena.dev.modular_avatar.core
|
||||
|
||||
private ArmatureLockController _lockController;
|
||||
|
||||
internal Transform MapBone(Transform bone)
|
||||
{
|
||||
var relPath = RuntimeUtil.RelativePath(gameObject, bone.gameObject);
|
||||
|
||||
if (relPath == null) throw new ArgumentException("Bone is not a child of this component");
|
||||
if (relPath == "") return mergeTarget.Get(this).transform;
|
||||
|
||||
var segments = relPath.Split('/');
|
||||
|
||||
var pointer = mergeTarget.Get(this).transform;
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (!segment.StartsWith(prefix) || !segment.EndsWith(suffix)) return null;
|
||||
var targetObjectName = segment.Substring(prefix.Length,
|
||||
segment.Length - prefix.Length - suffix.Length);
|
||||
pointer = pointer.Find(targetObjectName);
|
||||
}
|
||||
|
||||
return pointer;
|
||||
}
|
||||
|
||||
internal Transform FindCorrespondingBone(Transform bone, Transform baseParent)
|
||||
{
|
||||
var childName = bone.gameObject.name;
|
||||
@ -161,6 +183,9 @@ namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
foreach (Transform t in merge)
|
||||
{
|
||||
var subMerge = t.GetComponent<ModularAvatarMergeArmature>();
|
||||
if (subMerge != null && subMerge != this) continue;
|
||||
|
||||
var baseChild = FindCorrespondingBone(t, baseBone);
|
||||
if (baseChild != null)
|
||||
{
|
||||
@ -196,6 +221,12 @@ namespace nadena.dev.modular_avatar.core
|
||||
prefix = mergeName.Substring(0, prefixLength);
|
||||
suffix = mergeName.Substring(mergeName.Length - suffixLength);
|
||||
|
||||
if (prefix == "J_Bip_C_")
|
||||
{
|
||||
// VRM workaround
|
||||
prefix = "J_Bip_";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(prefix) || !string.IsNullOrEmpty(suffix))
|
||||
{
|
||||
RuntimeUtil.MarkDirty(this);
|
||||
|
Loading…
Reference in New Issue
Block a user