mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 18:55:06 +08:00
fix: fix issues with nested armature confusion in Easy Setup Outfit (#469)
This fixes issues with nested armature confusion by changing the name of the Armature object (only).
This commit is contained in:
parent
56119c0779
commit
7e8aa3f5f1
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e8745411e14ca634e87ffbf19723aab7
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,60 @@
|
|||||||
|
using modular_avatar_tests;
|
||||||
|
using nadena.dev.modular_avatar.core.editor;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using VRC.SDK3.Avatars.Components;
|
||||||
|
using UnityObject = UnityEngine.Object;
|
||||||
|
|
||||||
|
public class ArmatureConfusionTest : TestBase
|
||||||
|
{
|
||||||
|
[TearDown]
|
||||||
|
public void TearDown()
|
||||||
|
{
|
||||||
|
ESOErrorWindow.Suppress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestArmatureConfusionWorkaround()
|
||||||
|
{
|
||||||
|
ESOErrorWindow.Suppress = true;
|
||||||
|
|
||||||
|
// Arrange for a confused armature
|
||||||
|
var outer = CreatePrefab("shapell.fbx");
|
||||||
|
var inner = CreatePrefab("shapell.fbx");
|
||||||
|
|
||||||
|
var outerAnimator = outer.GetComponent<Animator>();
|
||||||
|
outer.AddComponent<VRCAvatarDescriptor>();
|
||||||
|
|
||||||
|
inner.gameObject.name = "inner";
|
||||||
|
inner.transform.parent = outer.transform;
|
||||||
|
|
||||||
|
// Unity seems to determine which armature is the "true" armature by counting the number of bones that match
|
||||||
|
// the humanoid description, and finding the root which has the most matches. Let's confuse it a bit by removing
|
||||||
|
// some non-humanoid bones from the outer armature.
|
||||||
|
var outerTarget = outer.transform.Find("Armature/Hips/Tail");
|
||||||
|
UnityObject.DestroyImmediate(outerTarget.gameObject);
|
||||||
|
|
||||||
|
// Clear animator cache
|
||||||
|
var avatar = outerAnimator.avatar;
|
||||||
|
outerAnimator.avatar = null;
|
||||||
|
// ReSharper disable once Unity.InefficientPropertyAccess
|
||||||
|
outerAnimator.avatar = avatar;
|
||||||
|
|
||||||
|
// Verify that we're well and confused now
|
||||||
|
Assert.AreSame(
|
||||||
|
outerAnimator.GetBoneTransform(HumanBodyBones.Hips),
|
||||||
|
inner.transform.Find("Armature/Hips")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now do a setup outfit operation
|
||||||
|
Selection.activeGameObject = inner;
|
||||||
|
EasySetupOutfit.SetupOutfit(new MenuCommand(inner));
|
||||||
|
|
||||||
|
// Verify that we're not confused anymore
|
||||||
|
Assert.AreSame(
|
||||||
|
outerAnimator.GetBoneTransform(HumanBodyBones.Hips),
|
||||||
|
outer.transform.Find("Armature/Hips")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 01ff1e86623a409ba5248c5fb6a0b53e
|
||||||
|
timeCreated: 1696244575
|
@ -0,0 +1,2 @@
|
|||||||
|
shapell.fbx is from Shapell (https://booth.pm/ja/items/1349366) by lowteq
|
||||||
|
Released under CC0 1.0 Universal (CC0 1.0) (https://creativecommons.org/publicdomain/zero/1.0/deed.ja)
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c5e311d0ce414f56afac9ccff53b16c4
|
||||||
|
timeCreated: 1696245763
|
@ -0,0 +1,77 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!21 &2100000
|
||||||
|
Material:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: material
|
||||||
|
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_ShaderKeywords:
|
||||||
|
m_LightmapFlags: 4
|
||||||
|
m_EnableInstancingVariants: 0
|
||||||
|
m_DoubleSidedGI: 0
|
||||||
|
m_CustomRenderQueue: -1
|
||||||
|
stringTagMap: {}
|
||||||
|
disabledShaderPasses: []
|
||||||
|
m_SavedProperties:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TexEnvs:
|
||||||
|
- _BumpMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailAlbedoMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailMask:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailNormalMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _EmissionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MainTex:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MetallicGlossMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _OcclusionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _ParallaxMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
m_Floats:
|
||||||
|
- _BumpScale: 1
|
||||||
|
- _Cutoff: 0.5
|
||||||
|
- _DetailNormalMapScale: 1
|
||||||
|
- _DstBlend: 0
|
||||||
|
- _GlossMapScale: 1
|
||||||
|
- _Glossiness: 0.5
|
||||||
|
- _GlossyReflections: 1
|
||||||
|
- _Metallic: 0
|
||||||
|
- _Mode: 0
|
||||||
|
- _OcclusionStrength: 1
|
||||||
|
- _Parallax: 0.02
|
||||||
|
- _SmoothnessTextureChannel: 0
|
||||||
|
- _SpecularHighlights: 1
|
||||||
|
- _SrcBlend: 1
|
||||||
|
- _UVSec: 0
|
||||||
|
- _ZWrite: 1
|
||||||
|
m_Colors:
|
||||||
|
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 14fe6d8ede57a1a45955b9a0181223ae
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 2100000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -11,10 +11,16 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
{
|
{
|
||||||
private string header;
|
private string header;
|
||||||
private string[] messageGroups;
|
private string[] messageGroups;
|
||||||
private static readonly GUIStyle buttonStyle, labelStyle;
|
private static GUIStyle buttonStyle, labelStyle;
|
||||||
private const float SeparatorSize = 6f;
|
private const float SeparatorSize = 6f;
|
||||||
|
|
||||||
|
internal static bool Suppress = false;
|
||||||
|
|
||||||
static ESOErrorWindow()
|
static ESOErrorWindow()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void InitStyles()
|
||||||
{
|
{
|
||||||
buttonStyle = EditorStyles.miniButtonRight;
|
buttonStyle = EditorStyles.miniButtonRight;
|
||||||
labelStyle = EditorStyles.label;
|
labelStyle = EditorStyles.label;
|
||||||
@ -24,15 +30,15 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
buttonStyle.fixedHeight = EditorGUIUtility.singleLineHeight * 1.5f;
|
buttonStyle.fixedHeight = EditorGUIUtility.singleLineHeight * 1.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void Show(
|
internal static void Show(
|
||||||
string header,
|
string header,
|
||||||
string[] messageGroups
|
string[] messageGroups
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
if (Suppress) return;
|
||||||
|
|
||||||
|
InitStyles();
|
||||||
|
|
||||||
var window = CreateInstance<ESOErrorWindow>();
|
var window = CreateInstance<ESOErrorWindow>();
|
||||||
window.titleContent = new GUIContent("Setup Outfit");
|
window.titleContent = new GUIContent("Setup Outfit");
|
||||||
window.header = header;
|
window.header = header;
|
||||||
@ -99,14 +105,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class EasySetupOutfit
|
internal static class EasySetupOutfit
|
||||||
{
|
{
|
||||||
private const int PRIORITY = 49;
|
private const int PRIORITY = 49;
|
||||||
private static string[] errorMessageGroups;
|
private static string[] errorMessageGroups;
|
||||||
private static string errorHeader;
|
private static string errorHeader;
|
||||||
|
|
||||||
[MenuItem("GameObject/ModularAvatar/Setup Outfit", false, PRIORITY)]
|
[MenuItem("GameObject/ModularAvatar/Setup Outfit", false, PRIORITY)]
|
||||||
static void SetupOutfit(MenuCommand cmd)
|
internal static void SetupOutfit(MenuCommand cmd)
|
||||||
{
|
{
|
||||||
if (!ValidateSetupOutfit())
|
if (!ValidateSetupOutfit())
|
||||||
{
|
{
|
||||||
@ -130,6 +136,23 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
merge.LockMode = ArmatureLockMode.BaseToMerge;
|
merge.LockMode = ArmatureLockMode.BaseToMerge;
|
||||||
merge.InferPrefixSuffix();
|
merge.InferPrefixSuffix();
|
||||||
HeuristicBoneMapper.RenameBonesByHeuristic(merge);
|
HeuristicBoneMapper.RenameBonesByHeuristic(merge);
|
||||||
|
|
||||||
|
var avatarRootMatchingArmature = avatarRoot.transform.Find(outfitArmature.gameObject.name);
|
||||||
|
if (merge.prefix == "" && merge.suffix == "" && avatarRootMatchingArmature != null)
|
||||||
|
{
|
||||||
|
// We have an armature whose names exactly match the root armature - this can cause some serious
|
||||||
|
// confusion in Unity's humanoid armature matching system. Fortunately, we can avoid this by
|
||||||
|
// renaming a bone close to the root; this will ensure the number of matching bones is small, and
|
||||||
|
// Unity's heuristics (apparently) will choose the base avatar's armature as the "true" armature.
|
||||||
|
outfitArmature.name += ".1";
|
||||||
|
|
||||||
|
// Also make sure to refresh the avatar's animator humanoid bone cache.
|
||||||
|
var avatarAnimator = avatarRoot.GetComponent<Animator>();
|
||||||
|
var humanDescription = avatarAnimator.avatar;
|
||||||
|
avatarAnimator.avatar = null;
|
||||||
|
// ReSharper disable once Unity.InefficientPropertyAccess
|
||||||
|
avatarAnimator.avatar = humanDescription;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outfitRoot != null
|
if (outfitRoot != null
|
||||||
@ -298,7 +321,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
};
|
};
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
avatarHips = avatarAnimator.GetBoneTransform(HumanBodyBones.Hips)?.gameObject;
|
avatarHips = avatarAnimator.GetBoneTransform(HumanBodyBones.Hips)?.gameObject;
|
||||||
|
|
||||||
if (avatarHips == null)
|
if (avatarHips == null)
|
||||||
@ -334,7 +357,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
hipsCandidates.Add(avatarHips.name);
|
hipsCandidates.Add(avatarHips.name);
|
||||||
|
|
||||||
// If that doesn't work out, we'll check for heuristic bone mapper mappings.
|
// If that doesn't work out, we'll check for heuristic bone mapper mappings.
|
||||||
foreach (var hbm in HeuristicBoneMapper.BoneToNameMap[HumanBodyBones.Hips])
|
foreach (var hbm in HeuristicBoneMapper.BoneToNameMap[HumanBodyBones.Hips])
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user