mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-04-26 06:19:01 +08:00
feat: add support for unmangled names and nested merging in MergeArmature
This commit is contained in:
parent
5231b75055
commit
51b73fec72
@ -0,0 +1,109 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using nadena.dev.modular_avatar.core;
|
||||||
|
using nadena.dev.modular_avatar.core.editor;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using UnityEngine;
|
||||||
|
using VRC.SDK3.Avatars.Components;
|
||||||
|
|
||||||
|
namespace modular_avatar_tests.MergeArmatureTests
|
||||||
|
{
|
||||||
|
public class TestComponentA : MonoBehaviour
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestComponentB : MonoBehaviour
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MarkDestroy : MonoBehaviour
|
||||||
|
{
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
Debug.Log("blah");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MultiLevelMergeTest : TestBase
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void mergeProcessesInTopoOrder()
|
||||||
|
{
|
||||||
|
var root = CreateRoot("root");
|
||||||
|
var armature = CreateChild(root, "Armature");
|
||||||
|
var bone = CreateChild(armature, "Bone");
|
||||||
|
|
||||||
|
var merge1 = CreateChild(root, "merge1");
|
||||||
|
var m1_bone = CreateChild(merge1, "Bone");
|
||||||
|
var m1_leaf = CreateChild(m1_bone, "leaf1");
|
||||||
|
var m1_leaf2 = CreateChild(m1_leaf, "leaf2");
|
||||||
|
|
||||||
|
var merge2 = CreateChild(root, "merge2");
|
||||||
|
var m2_bone = CreateChild(merge2, "Bone");
|
||||||
|
var m2_leaf = CreateChild(m2_bone, "leaf1");
|
||||||
|
var m2_leaf3 = CreateChild(m2_leaf, "leaf3");
|
||||||
|
|
||||||
|
var ma1 = merge1.AddComponent<ModularAvatarMergeArmature>();
|
||||||
|
ma1.mergeTarget.referencePath = RuntimeUtil.AvatarRootPath(armature);
|
||||||
|
|
||||||
|
var ma2 = merge2.AddComponent<ModularAvatarMergeArmature>();
|
||||||
|
ma2.mergeTarget.referencePath = RuntimeUtil.AvatarRootPath(merge1);
|
||||||
|
|
||||||
|
m1_leaf2.AddComponent<TestComponentA>();
|
||||||
|
m2_leaf3.AddComponent<TestComponentB>();
|
||||||
|
|
||||||
|
BuildContext context = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
|
||||||
|
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
||||||
|
|
||||||
|
Assert.IsTrue(bone.GetComponentInChildren<TestComponentA>() != null);
|
||||||
|
Assert.IsTrue(bone.GetComponentInChildren<TestComponentB>() != null);
|
||||||
|
Assert.IsTrue(m2_leaf3.GetComponentsInParent<Transform>().Contains(m1_leaf.transform));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void canDisableNameMangling()
|
||||||
|
{
|
||||||
|
var root = CreateRoot("root");
|
||||||
|
var armature = CreateChild(root, "Armature");
|
||||||
|
var bone = CreateChild(armature, "Bone");
|
||||||
|
|
||||||
|
var merge = CreateChild(root, "merge");
|
||||||
|
var m_bone = CreateChild(merge, "Bone");
|
||||||
|
var m_leaf = CreateChild(m_bone, "leaf");
|
||||||
|
|
||||||
|
//m_bone.AddComponent<MarkDestroy>();
|
||||||
|
|
||||||
|
var ma = merge.AddComponent<ModularAvatarMergeArmature>();
|
||||||
|
ma.mergeTarget.referencePath = RuntimeUtil.AvatarRootPath(armature);
|
||||||
|
ma.mangleNames = false;
|
||||||
|
|
||||||
|
BuildContext context = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
|
||||||
|
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
||||||
|
|
||||||
|
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass
|
||||||
|
Assert.IsTrue(m_leaf.transform.name == "leaf");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void manglesByDefault()
|
||||||
|
{
|
||||||
|
var root = CreateRoot("root");
|
||||||
|
var armature = CreateChild(root, "Armature");
|
||||||
|
var bone = CreateChild(armature, "Bone");
|
||||||
|
|
||||||
|
var merge = CreateChild(root, "merge");
|
||||||
|
var m_bone = CreateChild(merge, "Bone");
|
||||||
|
var m_leaf = CreateChild(m_bone, "leaf");
|
||||||
|
|
||||||
|
var ma = merge.AddComponent<ModularAvatarMergeArmature>();
|
||||||
|
ma.mergeTarget.referencePath = RuntimeUtil.AvatarRootPath(armature);
|
||||||
|
|
||||||
|
BuildContext context = new BuildContext(root.GetComponent<VRCAvatarDescriptor>());
|
||||||
|
new MergeArmatureHook().OnPreprocessAvatar(context, root);
|
||||||
|
|
||||||
|
Assert.IsTrue(m_bone == null); // destroyed by retargeting pass
|
||||||
|
Assert.IsTrue(m_leaf.transform.name != "leaf");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 705b64bd35654fcd819c614fca5caff4
|
||||||
|
timeCreated: 1690614538
|
@ -8,7 +8,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
[CustomEditor(typeof(ModularAvatarMergeArmature))]
|
[CustomEditor(typeof(ModularAvatarMergeArmature))]
|
||||||
internal class MergeArmatureEditor : MAEditorBase
|
internal class MergeArmatureEditor : MAEditorBase
|
||||||
{
|
{
|
||||||
private SerializedProperty prop_mergeTarget, prop_prefix, prop_suffix, prop_locked;
|
private SerializedProperty prop_mergeTarget, prop_prefix, prop_suffix, prop_locked, prop_mangleNames;
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
@ -16,6 +16,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
prop_prefix = serializedObject.FindProperty(nameof(ModularAvatarMergeArmature.prefix));
|
prop_prefix = serializedObject.FindProperty(nameof(ModularAvatarMergeArmature.prefix));
|
||||||
prop_suffix = serializedObject.FindProperty(nameof(ModularAvatarMergeArmature.suffix));
|
prop_suffix = serializedObject.FindProperty(nameof(ModularAvatarMergeArmature.suffix));
|
||||||
prop_locked = serializedObject.FindProperty(nameof(ModularAvatarMergeArmature.locked));
|
prop_locked = serializedObject.FindProperty(nameof(ModularAvatarMergeArmature.locked));
|
||||||
|
prop_mangleNames = serializedObject.FindProperty(nameof(ModularAvatarMergeArmature.mangleNames));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowParametersUI()
|
private void ShowParametersUI()
|
||||||
@ -25,6 +26,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
EditorGUILayout.PropertyField(prop_mergeTarget, G("merge_armature.merge_target"));
|
EditorGUILayout.PropertyField(prop_mergeTarget, G("merge_armature.merge_target"));
|
||||||
EditorGUILayout.PropertyField(prop_prefix, G("merge_armature.prefix"));
|
EditorGUILayout.PropertyField(prop_prefix, G("merge_armature.prefix"));
|
||||||
EditorGUILayout.PropertyField(prop_suffix, G("merge_armature.suffix"));
|
EditorGUILayout.PropertyField(prop_suffix, G("merge_armature.suffix"));
|
||||||
|
EditorGUILayout.PropertyField(prop_mangleNames, G("merge_armature.mangle_names"));
|
||||||
EditorGUILayout.PropertyField(prop_locked, G("merge_armature.locked"));
|
EditorGUILayout.PropertyField(prop_locked, G("merge_armature.locked"));
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
@ -39,6 +39,8 @@
|
|||||||
"merge_armature.locked.tooltip": "Lock the position of this armature's bones to the target armature (and vice versa). Useful for creating animations.",
|
"merge_armature.locked.tooltip": "Lock the position of this armature's bones to the target armature (and vice versa). Useful for creating animations.",
|
||||||
"merge_armature.adjust_names": "Adjust bone names to match target",
|
"merge_armature.adjust_names": "Adjust bone names to match target",
|
||||||
"merge_armature.adjust_names.tooltip": "Changes bone names to match the target avatar. Useful for porting outfits from one avatar to another.",
|
"merge_armature.adjust_names.tooltip": "Changes bone names to match the target avatar. Useful for porting outfits from one avatar to another.",
|
||||||
|
"merge_armature.mangle_names": "Avoid name collisions",
|
||||||
|
"merge_armature.mangle_names.tooltip": "Avoid name collisions with other assets by mangling newly added bone names.",
|
||||||
"path_mode.Relative": "Relative to this object",
|
"path_mode.Relative": "Relative to this object",
|
||||||
"path_mode.Absolute": "Absolute (based on avatar root)",
|
"path_mode.Absolute": "Absolute (based on avatar root)",
|
||||||
"merge_animator.animator": "Animator to merge",
|
"merge_animator.animator": "Animator to merge",
|
||||||
|
@ -38,6 +38,8 @@
|
|||||||
"merge_armature.locked.tooltip": "このオブジェクトのボーンを統合先のボーンに常に相互的に位置を合わせる。アニメーション制作向け",
|
"merge_armature.locked.tooltip": "このオブジェクトのボーンを統合先のボーンに常に相互的に位置を合わせる。アニメーション制作向け",
|
||||||
"merge_armature.adjust_names": "ボーン名を統合先に合わせる",
|
"merge_armature.adjust_names": "ボーン名を統合先に合わせる",
|
||||||
"merge_armature.adjust_names.tooltip": "統合先のボーン名に合わせて、衣装のボーン名を合わせて変更します。統合先アバターに非対応の衣装導入向け機能です。",
|
"merge_armature.adjust_names.tooltip": "統合先のボーン名に合わせて、衣装のボーン名を合わせて変更します。統合先アバターに非対応の衣装導入向け機能です。",
|
||||||
|
"merge_armature.mangle_names": "名前かぶりを回避",
|
||||||
|
"merge_armature.mangle_names.tooltip": "ほかのアセットとの名前かぶりを裂けるため、新規ボーンの名前を自動で変更する",
|
||||||
"path_mode.Relative": "相対的(このオブジェクトからのパスを使用)",
|
"path_mode.Relative": "相対的(このオブジェクトからのパスを使用)",
|
||||||
"path_mode.Absolute": "絶対的(アバタールートからのパスを使用)",
|
"path_mode.Absolute": "絶対的(アバタールートからのパスを使用)",
|
||||||
"merge_animator.animator": "統合されるアニメーター",
|
"merge_animator.animator": "統合されるアニメーター",
|
||||||
|
@ -46,19 +46,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
var mergeArmatures = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>(true);
|
var mergeArmatures =
|
||||||
|
avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>(true);
|
||||||
|
|
||||||
foreach (var mergeArmature in mergeArmatures)
|
TopoProcessMergeArmatures(mergeArmatures);
|
||||||
{
|
|
||||||
BuildReport.ReportingObject(mergeArmature, () =>
|
|
||||||
{
|
|
||||||
mergedObjects.Clear();
|
|
||||||
thisPassAdded.Clear();
|
|
||||||
MergeArmature(mergeArmature);
|
|
||||||
PruneDuplicatePhysBones();
|
|
||||||
UnityEngine.Object.DestroyImmediate(mergeArmature);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBone>(true))
|
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBone>(true))
|
||||||
{
|
{
|
||||||
@ -86,6 +77,90 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
new RetargetMeshes().OnPreprocessAvatar(avatarGameObject, context);
|
new RetargetMeshes().OnPreprocessAvatar(avatarGameObject, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TopoProcessMergeArmatures(ModularAvatarMergeArmature[] mergeArmatures)
|
||||||
|
{
|
||||||
|
Dictionary<ModularAvatarMergeArmature, List<ModularAvatarMergeArmature>> runsBefore
|
||||||
|
= new Dictionary<ModularAvatarMergeArmature, List<ModularAvatarMergeArmature>>();
|
||||||
|
|
||||||
|
foreach (var config in mergeArmatures)
|
||||||
|
{
|
||||||
|
// TODO - assert that we're not nesting merge armatures?
|
||||||
|
|
||||||
|
var target = config.mergeTargetObject;
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
// TODO - report error
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentConfig = target.GetComponentInParent<ModularAvatarMergeArmature>();
|
||||||
|
if (parentConfig != null)
|
||||||
|
{
|
||||||
|
if (!runsBefore.ContainsKey(parentConfig))
|
||||||
|
{
|
||||||
|
runsBefore[parentConfig] = new List<ModularAvatarMergeArmature>();
|
||||||
|
}
|
||||||
|
|
||||||
|
runsBefore[parentConfig].Add(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<ModularAvatarMergeArmature> visited = new HashSet<ModularAvatarMergeArmature>();
|
||||||
|
Stack<ModularAvatarMergeArmature> visitStack = new Stack<ModularAvatarMergeArmature>();
|
||||||
|
foreach (var next in mergeArmatures)
|
||||||
|
{
|
||||||
|
TopoLoop(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopoLoop(ModularAvatarMergeArmature config)
|
||||||
|
{
|
||||||
|
if (visited.Contains(config)) return;
|
||||||
|
if (visitStack.Contains(config))
|
||||||
|
{
|
||||||
|
BuildReport.LogFatal("merge_armature.circular_dependency", new string[0], config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitStack.Push(config);
|
||||||
|
var target = config.mergeTargetObject;
|
||||||
|
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
if (runsBefore.TryGetValue(config, out var predecessors))
|
||||||
|
{
|
||||||
|
foreach (var priorConfig in predecessors)
|
||||||
|
{
|
||||||
|
TopoLoop(priorConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MergeArmatureWithReporting(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitStack.Pop();
|
||||||
|
visited.Add(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MergeArmatureWithReporting(ModularAvatarMergeArmature config)
|
||||||
|
{
|
||||||
|
var target = config.mergeTargetObject;
|
||||||
|
|
||||||
|
while (BoneDatabase.IsRetargetable(target.transform))
|
||||||
|
{
|
||||||
|
target = target.transform.parent.gameObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildReport.ReportingObject(config, () =>
|
||||||
|
{
|
||||||
|
mergedObjects.Clear();
|
||||||
|
thisPassAdded.Clear();
|
||||||
|
MergeArmature(config, target);
|
||||||
|
PruneDuplicatePhysBones();
|
||||||
|
UnityEngine.Object.DestroyImmediate(config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void RetainBoneReferences(Component c)
|
private void RetainBoneReferences(Component c)
|
||||||
{
|
{
|
||||||
if (c == null) return;
|
if (c == null) return;
|
||||||
@ -168,14 +243,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
private Dictionary<string, List<GameObject>>
|
private Dictionary<string, List<GameObject>>
|
||||||
activationPathMappings = new Dictionary<string, List<GameObject>>();
|
activationPathMappings = new Dictionary<string, List<GameObject>>();
|
||||||
|
|
||||||
private void MergeArmature(ModularAvatarMergeArmature mergeArmature)
|
private void MergeArmature(ModularAvatarMergeArmature mergeArmature, GameObject mergeTargetObject)
|
||||||
{
|
{
|
||||||
// TODO: error reporting framework?
|
// TODO: error reporting?
|
||||||
if (mergeArmature.mergeTargetObject == null) return;
|
if (mergeTargetObject == null) return;
|
||||||
|
|
||||||
GatherActiveStatePaths(mergeArmature.transform);
|
GatherActiveStatePaths(mergeArmature.transform);
|
||||||
|
|
||||||
RecursiveMerge(mergeArmature, mergeArmature.gameObject, mergeArmature.mergeTargetObject.gameObject, true);
|
RecursiveMerge(mergeArmature, mergeArmature.gameObject, mergeTargetObject, true);
|
||||||
|
|
||||||
FixupAnimations();
|
FixupAnimations();
|
||||||
}
|
}
|
||||||
@ -329,7 +404,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
src.transform.SetParent(mergedSrcBone.transform, true);
|
src.transform.SetParent(mergedSrcBone.transform, true);
|
||||||
src.name = src.name + "$" + Guid.NewGuid();
|
if (config.mangleNames)
|
||||||
|
{
|
||||||
|
src.name = src.name + "$" + Guid.NewGuid();
|
||||||
|
}
|
||||||
|
|
||||||
src.GetOrAddComponent<ModularAvatarPBBlocker>();
|
src.GetOrAddComponent<ModularAvatarPBBlocker>();
|
||||||
mergedSrcBone = src;
|
mergedSrcBone = src;
|
||||||
|
|
||||||
|
@ -34,37 +34,42 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
internal static class BoneDatabase
|
internal static class BoneDatabase
|
||||||
{
|
{
|
||||||
private static Dictionary<Transform, bool> IsRetargetable = new Dictionary<Transform, bool>();
|
private static Dictionary<Transform, bool> m_IsRetargetable = new Dictionary<Transform, bool>();
|
||||||
|
|
||||||
internal static void ResetBones()
|
internal static void ResetBones()
|
||||||
{
|
{
|
||||||
IsRetargetable.Clear();
|
m_IsRetargetable.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsRetargetable(Transform t)
|
||||||
|
{
|
||||||
|
return m_IsRetargetable.TryGetValue(t, out var result) && result;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void AddMergedBone(Transform bone)
|
internal static void AddMergedBone(Transform bone)
|
||||||
{
|
{
|
||||||
IsRetargetable[bone] = true;
|
m_IsRetargetable[bone] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void RetainMergedBone(Transform bone)
|
internal static void RetainMergedBone(Transform bone)
|
||||||
{
|
{
|
||||||
if (bone == null) return;
|
if (bone == null) return;
|
||||||
if (IsRetargetable.ContainsKey(bone)) IsRetargetable[bone] = false;
|
if (m_IsRetargetable.ContainsKey(bone)) m_IsRetargetable[bone] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Transform GetRetargetedBone(Transform bone)
|
internal static Transform GetRetargetedBone(Transform bone)
|
||||||
{
|
{
|
||||||
if (bone == null || !IsRetargetable.ContainsKey(bone)) return null;
|
if (bone == null || !m_IsRetargetable.ContainsKey(bone)) return null;
|
||||||
|
|
||||||
while (bone != null && IsRetargetable.ContainsKey(bone) && IsRetargetable[bone]) bone = bone.parent;
|
while (bone != null && m_IsRetargetable.ContainsKey(bone) && m_IsRetargetable[bone]) bone = bone.parent;
|
||||||
|
|
||||||
if (IsRetargetable.ContainsKey(bone)) return null;
|
if (m_IsRetargetable.ContainsKey(bone)) return null;
|
||||||
return bone;
|
return bone;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IEnumerable<KeyValuePair<Transform, Transform>> GetRetargetedBones()
|
internal static IEnumerable<KeyValuePair<Transform, Transform>> GetRetargetedBones()
|
||||||
{
|
{
|
||||||
return IsRetargetable.Where((kvp) => kvp.Value)
|
return m_IsRetargetable.Where((kvp) => kvp.Value)
|
||||||
.Select(kvp => new KeyValuePair<Transform, Transform>(kvp.Key, GetRetargetedBone(kvp.Key)))
|
.Select(kvp => new KeyValuePair<Transform, Transform>(kvp.Key, GetRetargetedBone(kvp.Key)))
|
||||||
.Where(kvp => kvp.Value != null);
|
.Where(kvp => kvp.Value != null);
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ using JetBrains.Annotations;
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.Animations;
|
using UnityEditor.Animations;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using VRC.SDK3.Avatars.Components;
|
||||||
using VRC.SDKBase.Editor.BuildPipeline;
|
using VRC.SDKBase.Editor.BuildPipeline;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
@ -238,6 +239,18 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
return ValidateExpressionMenuIconResult.Success;
|
return ValidateExpressionMenuIconResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static IEnumerable<T> FindComponentInParents<T>(this Component t) where T : Component
|
||||||
|
{
|
||||||
|
Transform ptr = t.transform.parent;
|
||||||
|
while (ptr != null)
|
||||||
|
{
|
||||||
|
var component = ptr.GetComponent<T>();
|
||||||
|
if (component != null) yield return component;
|
||||||
|
if (ptr.GetComponent<VRCAvatarDescriptor>() != null) break;
|
||||||
|
ptr = ptr.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal static IEnumerable<AnimatorState> States(AnimatorController ac)
|
internal static IEnumerable<AnimatorState> States(AnimatorController ac)
|
||||||
{
|
{
|
||||||
HashSet<AnimatorStateMachine> visitedStateMachines = new HashSet<AnimatorStateMachine>();
|
HashSet<AnimatorStateMachine> visitedStateMachines = new HashSet<AnimatorStateMachine>();
|
||||||
|
@ -40,12 +40,13 @@ namespace nadena.dev.modular_avatar.core
|
|||||||
private const float POS_EPSILON = 0.001f * 0.001f;
|
private const float POS_EPSILON = 0.001f * 0.001f;
|
||||||
private const float ROT_EPSILON = 0.001f * 0.001f;
|
private const float ROT_EPSILON = 0.001f * 0.001f;
|
||||||
|
|
||||||
public AvatarObjectReference mergeTarget;
|
public AvatarObjectReference mergeTarget = new AvatarObjectReference();
|
||||||
public GameObject mergeTargetObject => mergeTarget.Get(this);
|
public GameObject mergeTargetObject => mergeTarget.Get(this);
|
||||||
|
|
||||||
public string prefix;
|
public string prefix = "";
|
||||||
public string suffix;
|
public string suffix = "";
|
||||||
public bool locked;
|
public bool locked = false;
|
||||||
|
public bool mangleNames = true;
|
||||||
|
|
||||||
private class BoneBinding
|
private class BoneBinding
|
||||||
{
|
{
|
||||||
|
@ -32,6 +32,9 @@ Merge Armature goes to a lot of trouble to ensure that components configured on
|
|||||||
Merge Armature will leave portions of the original hierarchy behind - specifically, if they contain any components other than Transforms, they will be retained, and otherwise will generally be deleted.
|
Merge Armature will leave portions of the original hierarchy behind - specifically, if they contain any components other than Transforms, they will be retained, and otherwise will generally be deleted.
|
||||||
Where necessary, PhysBone objects will have their targets updated, and ParentConstraints may be created as necessary to make things Just Work (tm).
|
Where necessary, PhysBone objects will have their targets updated, and ParentConstraints may be created as necessary to make things Just Work (tm).
|
||||||
|
|
||||||
|
As of Modular Avatar 1.7.0, it is now possible to perform nested merges - that is, merge an armature A into B, then
|
||||||
|
merge B into C. Modular Avatar will automatically determine the correct order to apply these merges.
|
||||||
|
|
||||||
## Locked mode
|
## Locked mode
|
||||||
|
|
||||||
If the locked option is enabled, the position and rotation of the merged bones will be locked to their parents in the editor. This is a two-way relationship; if you move the merged bone, the avatar's bone will move, and vice versa.
|
If the locked option is enabled, the position and rotation of the merged bones will be locked to their parents in the editor. This is a two-way relationship; if you move the merged bone, the avatar's bone will move, and vice versa.
|
||||||
@ -48,3 +51,12 @@ This allows the merge armature component to automatically restore its Merge Targ
|
|||||||
Since Merge Animator will attempt to match bones by name, just attaching it won't always work to make an outfit designed for one avatar work with another avatar.
|
Since Merge Animator will attempt to match bones by name, just attaching it won't always work to make an outfit designed for one avatar work with another avatar.
|
||||||
You can click the "Adjust bone names to match target" button to attempt to rename bones in the outfit to match the base avatar it's currently attached to.
|
You can click the "Adjust bone names to match target" button to attempt to rename bones in the outfit to match the base avatar it's currently attached to.
|
||||||
This will be done automatically if you added the Merge Armature component using the "Setup Outfit" menu item.
|
This will be done automatically if you added the Merge Armature component using the "Setup Outfit" menu item.
|
||||||
|
|
||||||
|
## Avoid name collisions
|
||||||
|
|
||||||
|
Although merge armature merges bones that have names matching ones on the merge target, by default any _newly added_
|
||||||
|
bones that are unique to this new merged asset will have their names changed. This helps avoid conflicting with other
|
||||||
|
assets that also use merge armature, and which happen to have chosen the same bone name.
|
||||||
|
|
||||||
|
In some special circumstances, it can be helpful to disable this behavior. In those cases, you can uncheck the "avoid
|
||||||
|
name collisions" box.
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 27 KiB |
@ -35,6 +35,9 @@ Merge Armatureは衣装アセット専用のもので、Skinned Mesh Rendererの
|
|||||||
Transform以外のコンポーネントが入っているボーンがある場合、そのボーンが残ります。そのほかのボーンは原則として統合後に削除されます。
|
Transform以外のコンポーネントが入っているボーンがある場合、そのボーンが残ります。そのほかのボーンは原則として統合後に削除されます。
|
||||||
必要に応じてPhysBoneのターゲットが調整されたり、ParentConstraintが生成されることで、なんとなく動くようになります。
|
必要に応じてPhysBoneのターゲットが調整されたり、ParentConstraintが生成されることで、なんとなく動くようになります。
|
||||||
|
|
||||||
|
Modular Avatar 1.7.0以降、連鎖的に統合することができます。つまり、AをBに統合して、BをCに統合することができます。
|
||||||
|
統合参照に応じてModular Avatarが自動的に統合順序を計算します。
|
||||||
|
|
||||||
## 位置を固定
|
## 位置を固定
|
||||||
|
|
||||||
位置を固定を設定すると、統合先のボーンがエディタ上で元のボーンと同じ位置になります。これは総合的な関係で、どちらかのボーンが
|
位置を固定を設定すると、統合先のボーンがエディタ上で元のボーンと同じ位置になります。これは総合的な関係で、どちらかのボーンが
|
||||||
@ -51,3 +54,10 @@ Transform以外のコンポーネントが入っているボーンがある場
|
|||||||
Merge Animatorがボーンを名前で照合するので、つけるだけでは非対応衣装がうまく動かない場合があります。
|
Merge Animatorがボーンを名前で照合するので、つけるだけでは非対応衣装がうまく動かない場合があります。
|
||||||
対策として、「ボーン名を統合先に合わせる」ボタンを押すことで、衣装側のボーン名を自動的にアバターのボーン名に合わせようとします。
|
対策として、「ボーン名を統合先に合わせる」ボタンを押すことで、衣装側のボーン名を自動的にアバターのボーン名に合わせようとします。
|
||||||
なお、「Setup outfit」でMerge Armatureをつける場合はこの処理が自動的に走ります。
|
なお、「Setup outfit」でMerge Armatureをつける場合はこの処理が自動的に走ります。
|
||||||
|
|
||||||
|
## 名前かぶりを回避
|
||||||
|
|
||||||
|
統合先と一致する名前のボーンがある場合は統合しますが、統合元のボーンのうち統合先にないものは名前を変更した上で配置します。
|
||||||
|
これは、統合先と偶然同じ名前のボーンがある他のアセットと衝突することを避けるためです。
|
||||||
|
|
||||||
|
特殊な場合でこの処理を無効にしたい場合は「名前かぶりを回避」のチェックを外してください。
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 28 KiB |
Loading…
x
Reference in New Issue
Block a user