mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 18:55: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.mergeTarget.referencePath = RuntimeUtil.RelativePath(avatarRoot, avatarArmature.gameObject);
|
||||||
merge.LockMode = ArmatureLockMode.BaseToMerge;
|
merge.LockMode = ArmatureLockMode.BaseToMerge;
|
||||||
merge.InferPrefixSuffix();
|
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);
|
var avatarRootMatchingArmature = avatarRoot.transform.Find(outfitArmature.gameObject.name);
|
||||||
if (merge.prefix == "" && merge.suffix == "" && avatarRootMatchingArmature != null)
|
if (merge.prefix == "" && merge.suffix == "" && avatarRootMatchingArmature != null)
|
||||||
|
@ -226,15 +226,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
new[] {"UpperChest", "UChest"},
|
new[] {"UpperChest", "UChest"},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
internal static readonly Regex Regex_VRM_Bone = new Regex(@"^([LRC])_(.*)$");
|
||||||
|
|
||||||
internal static string NormalizeName(string name)
|
internal static string NormalizeName(string name)
|
||||||
{
|
{
|
||||||
name = name.ToLowerInvariant()
|
name = name.ToLowerInvariant();
|
||||||
.Replace("_", "")
|
name = Regex.Replace(name, "[0-9 ._]", "");
|
||||||
.Replace(".", "")
|
|
||||||
.Replace(" ", "");
|
|
||||||
name = Regex.Replace(name, "[0-9]", "");
|
|
||||||
|
|
||||||
return PAT_END_NUMBER.Replace(name, "");
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static readonly ImmutableDictionary<string, List<HumanBodyBones>> NameToBoneMap;
|
internal static readonly ImmutableDictionary<string, List<HumanBodyBones>> NameToBoneMap;
|
||||||
@ -259,6 +258,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
altName = match.Groups[1] + "." + altName;
|
altName = match.Groups[1] + "." + altName;
|
||||||
RegisterNameForBone(NormalizeName(altName), bone);
|
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,17 +300,22 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
internal static Dictionary<Transform, Transform> AssignBoneMappings(
|
internal static Dictionary<Transform, Transform> AssignBoneMappings(
|
||||||
ModularAvatarMergeArmature config,
|
ModularAvatarMergeArmature config,
|
||||||
GameObject src,
|
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>();
|
Dictionary<Transform, Transform> mappings = new Dictionary<Transform, Transform>();
|
||||||
List<Transform> heuristicAssignmentPass = new List<Transform>();
|
List<Transform> heuristicAssignmentPass = new List<Transform>();
|
||||||
|
|
||||||
|
if (unassigned == null)
|
||||||
|
{
|
||||||
|
unassigned = new HashSet<Transform>();
|
||||||
foreach (Transform child in newParent.transform)
|
foreach (Transform child in newParent.transform)
|
||||||
{
|
{
|
||||||
unassigned.Add(child);
|
unassigned.Add(child);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Transform child in src.transform)
|
foreach (Transform child in src.transform)
|
||||||
{
|
{
|
||||||
@ -341,7 +351,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
childName.Length - config.prefix.Length - config.suffix.Length);
|
childName.Length - config.prefix.Length - config.suffix.Length);
|
||||||
|
|
||||||
if (!NameToBoneMap.TryGetValue(
|
if (!NameToBoneMap.TryGetValue(
|
||||||
NormalizeName(targetObjectName.ToLowerInvariant()), out var bodyBones))
|
NormalizeName(targetObjectName), out var bodyBones))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -356,21 +366,38 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
break;
|
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;
|
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));
|
var target = config.mergeTarget.Get(RuntimeUtil.FindAvatarTransformInParents(config.transform));
|
||||||
if (target == null) return;
|
if (target == null) return;
|
||||||
|
|
||||||
|
if (skipped == null) skipped = new List<Transform>();
|
||||||
|
|
||||||
Traverse(config.transform, target.transform);
|
Traverse(config.transform, target.transform);
|
||||||
|
|
||||||
void Traverse(Transform src, Transform dst)
|
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)
|
foreach (var pair in mappings)
|
||||||
{
|
{
|
||||||
|
@ -160,7 +160,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
private void ForcePositionToBaseAvatar()
|
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 mergeTarget = mama.mergeTarget.Get(mama);
|
||||||
var xform_to_bone = new Dictionary<Transform, HumanBodyBones>();
|
var xform_to_bone = new Dictionary<Transform, HumanBodyBones>();
|
||||||
var bone_to_xform = new Dictionary<HumanBodyBones, Transform>();
|
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();
|
AdjustRootScale();
|
||||||
}
|
}
|
||||||
@ -220,12 +225,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
Undo.RecordObject(t_merge, "Merge Armature: Force outfit position");
|
Undo.RecordObject(t_merge, "Merge Armature: Force outfit position");
|
||||||
|
|
||||||
Debug.Log("=== Processing: " + t_merge.gameObject.name);
|
Debug.Log("Merge: " + t_merge.gameObject.name + " => " + t_target.gameObject.name);
|
||||||
|
|
||||||
if (!t_merge.IsChildOf(mama.transform))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("t_merge not a child of mama.transform");
|
|
||||||
}
|
|
||||||
|
|
||||||
t_merge.position = t_target.position;
|
t_merge.position = t_target.position;
|
||||||
if (posReset_adjustScale)
|
if (posReset_adjustScale)
|
||||||
@ -241,14 +241,27 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
t_merge.localRotation = t_target.localRotation;
|
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)
|
||||||
{
|
{
|
||||||
|
foreach (Transform t_child in traversalQueue.Dequeue())
|
||||||
|
{
|
||||||
|
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))
|
if (TryMatchChildBone(t_target, t_child, out var t_target_child))
|
||||||
{
|
{
|
||||||
Walk(t_child, t_target_child);
|
Walk(t_child, t_target_child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool TryMatchChildBone(Transform t_target, Transform t_child, out Transform t_target_child)
|
bool TryMatchChildBone(Transform t_target, Transform t_child, out Transform t_target_child)
|
||||||
{
|
{
|
||||||
|
@ -121,8 +121,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
foreach (var config in mergeArmatures)
|
foreach (var config in mergeArmatures)
|
||||||
{
|
{
|
||||||
// TODO - assert that we're not nesting merge armatures?
|
|
||||||
|
|
||||||
var target = config.mergeTargetObject;
|
var target = config.mergeTargetObject;
|
||||||
if (target == null)
|
if (target == null)
|
||||||
{
|
{
|
||||||
@ -149,6 +147,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
TopoLoop(next);
|
TopoLoop(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var next in mergeArmatures)
|
||||||
|
{
|
||||||
|
UnityEngine.Object.DestroyImmediate(next);
|
||||||
|
}
|
||||||
|
|
||||||
void TopoLoop(ModularAvatarMergeArmature config)
|
void TopoLoop(ModularAvatarMergeArmature config)
|
||||||
{
|
{
|
||||||
if (visited.Contains(config)) return;
|
if (visited.Contains(config)) return;
|
||||||
@ -196,7 +199,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
#if MA_VRCSDK3_AVATARS
|
#if MA_VRCSDK3_AVATARS
|
||||||
PruneDuplicatePhysBones();
|
PruneDuplicatePhysBones();
|
||||||
#endif
|
#endif
|
||||||
UnityEngine.Object.DestroyImmediate(config);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,6 +362,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
foreach (Transform child in children)
|
foreach (Transform child in children)
|
||||||
{
|
{
|
||||||
|
if (child.GetComponent <ModularAvatarMergeArmature>() != null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var childGameObject = child.gameObject;
|
var childGameObject = child.gameObject;
|
||||||
var childName = childGameObject.name;
|
var childName = childGameObject.name;
|
||||||
GameObject childNewParent = mergedSrcBone;
|
GameObject childNewParent = mergedSrcBone;
|
||||||
|
@ -26,6 +26,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using nadena.dev.modular_avatar.core.armature_lock;
|
using nadena.dev.modular_avatar.core.armature_lock;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Analytics;
|
||||||
using UnityEngine.Serialization;
|
using UnityEngine.Serialization;
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core
|
namespace nadena.dev.modular_avatar.core
|
||||||
@ -59,6 +60,27 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
|
|
||||||
private ArmatureLockController _lockController;
|
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)
|
internal Transform FindCorrespondingBone(Transform bone, Transform baseParent)
|
||||||
{
|
{
|
||||||
var childName = bone.gameObject.name;
|
var childName = bone.gameObject.name;
|
||||||
@ -161,6 +183,9 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
{
|
{
|
||||||
foreach (Transform t in merge)
|
foreach (Transform t in merge)
|
||||||
{
|
{
|
||||||
|
var subMerge = t.GetComponent<ModularAvatarMergeArmature>();
|
||||||
|
if (subMerge != null && subMerge != this) continue;
|
||||||
|
|
||||||
var baseChild = FindCorrespondingBone(t, baseBone);
|
var baseChild = FindCorrespondingBone(t, baseBone);
|
||||||
if (baseChild != null)
|
if (baseChild != null)
|
||||||
{
|
{
|
||||||
@ -196,6 +221,12 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
prefix = mergeName.Substring(0, prefixLength);
|
prefix = mergeName.Substring(0, prefixLength);
|
||||||
suffix = mergeName.Substring(mergeName.Length - suffixLength);
|
suffix = mergeName.Substring(mergeName.Length - suffixLength);
|
||||||
|
|
||||||
|
if (prefix == "J_Bip_C_")
|
||||||
|
{
|
||||||
|
// VRM workaround
|
||||||
|
prefix = "J_Bip_";
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(prefix) || !string.IsNullOrEmpty(suffix))
|
if (!string.IsNullOrEmpty(prefix) || !string.IsNullOrEmpty(suffix))
|
||||||
{
|
{
|
||||||
RuntimeUtil.MarkDirty(this);
|
RuntimeUtil.MarkDirty(this);
|
||||||
|
Loading…
Reference in New Issue
Block a user