From 2557972461148e098c4979999000bb29f6bf2da2 Mon Sep 17 00:00:00 2001 From: bd_ Date: Fri, 14 Mar 2025 20:44:50 -0700 Subject: [PATCH] feat: ensure that correct layers are toggled off in MMD worlds, even after messing with layer order (#1489) We make the assumption that the MMD world will _specifically_ be disabling layers 1 and 2. --- .github/ProjectRoot/vpm-manifest-2022.json | 4 +- CHANGELOG-PRERELEASE-jp.md | 3 + CHANGELOG-PRERELEASE.md | 3 + CHANGELOG-jp.md | 5 +- CHANGELOG.md | 3 + Editor/Animation/MMDRelayPass.cs | 234 +++++++++ Editor/Animation/MMDRelayPass.cs.meta | 3 + Editor/Inspector/MMDModeEditor.cs | 30 ++ Editor/Inspector/MMDModeEditor.cs.meta | 3 + Editor/PluginDefinition/PluginDefinition.cs | 3 + Runtime/ModularAvatarMMDLayerControl.cs | 23 + Runtime/ModularAvatarMMDLayerControl.cs.meta | 3 + UnitTests~/Animation/LayerPruningTest.cs | 15 +- UnitTests~/Animation/MMD.meta | 3 + UnitTests~/Animation/MMD/AC1.controller | 192 ++++++++ UnitTests~/Animation/MMD/AC1.controller.meta | 8 + UnitTests~/Animation/MMD/AC2.controller | 192 ++++++++ UnitTests~/Animation/MMD/AC2.controller.meta | 8 + UnitTests~/Animation/MMD/MMDHandlingTests.cs | 138 ++++++ .../Animation/MMD/MMDHandlingTests.cs.meta | 3 + .../Animation/MMD/MMDMode_MergeBefore.prefab | 380 +++++++++++++++ .../MMD/MMDMode_MergeBefore.prefab.meta | 7 + UnitTests~/Animation/MMD/MMDMode_Noop.prefab | 325 +++++++++++++ .../Animation/MMD/MMDMode_Noop.prefab.meta | 7 + .../Animation/MMD/MMDMode_Overrides.prefab | 325 +++++++++++++ .../MMD/MMDMode_Overrides.prefab.meta | 7 + .../Animation/MMD/MMDMode_Reactive.prefab | 454 ++++++++++++++++++ .../MMD/MMDMode_Reactive.prefab.meta | 7 + UnitTests~/Animation/MMD/Overrides.controller | 340 +++++++++++++ .../Animation/MMD/Overrides.controller.meta | 8 + UnitTests~/Animation/MMD/empty.anim | 53 ++ UnitTests~/Animation/MMD/empty.anim.meta | 8 + UnitTests~/Animation/MergeBlendTreeTest.cs | 3 +- UnitTests~/Animation/MergeOrderTest.cs | 3 +- .../MergeAnimatorReplacementTest.cs | 17 +- .../WriteDefaults/WriteDefaultsMergeTests.cs | 4 + .../SyncedLayerHandling.cs | 13 +- UnitTests~/TestBase.cs | 5 + docs~/docs/general-behavior/index.md | 7 + docs~/docs/general-behavior/mmd.md | 28 ++ docs~/docs/problems/index.md | 2 +- docs~/docs/reference/mmd-layer-control.md | 3 + docs~/docs/unity-2019/parameters-devmode.png | Bin 31219 -> 0 bytes docs~/docs/unity-2019/parameters-enduser.png | Bin 13338 -> 0 bytes .../current/general-behavior/index.md | 7 + .../current/general-behavior/mmd.md | 32 ++ .../current/reference/mmd-layer-control.md | 3 + 47 files changed, 2900 insertions(+), 24 deletions(-) create mode 100644 Editor/Animation/MMDRelayPass.cs create mode 100644 Editor/Animation/MMDRelayPass.cs.meta create mode 100644 Editor/Inspector/MMDModeEditor.cs create mode 100644 Editor/Inspector/MMDModeEditor.cs.meta create mode 100644 Runtime/ModularAvatarMMDLayerControl.cs create mode 100644 Runtime/ModularAvatarMMDLayerControl.cs.meta create mode 100644 UnitTests~/Animation/MMD.meta create mode 100644 UnitTests~/Animation/MMD/AC1.controller create mode 100644 UnitTests~/Animation/MMD/AC1.controller.meta create mode 100644 UnitTests~/Animation/MMD/AC2.controller create mode 100644 UnitTests~/Animation/MMD/AC2.controller.meta create mode 100644 UnitTests~/Animation/MMD/MMDHandlingTests.cs create mode 100644 UnitTests~/Animation/MMD/MMDHandlingTests.cs.meta create mode 100644 UnitTests~/Animation/MMD/MMDMode_MergeBefore.prefab create mode 100644 UnitTests~/Animation/MMD/MMDMode_MergeBefore.prefab.meta create mode 100644 UnitTests~/Animation/MMD/MMDMode_Noop.prefab create mode 100644 UnitTests~/Animation/MMD/MMDMode_Noop.prefab.meta create mode 100644 UnitTests~/Animation/MMD/MMDMode_Overrides.prefab create mode 100644 UnitTests~/Animation/MMD/MMDMode_Overrides.prefab.meta create mode 100644 UnitTests~/Animation/MMD/MMDMode_Reactive.prefab create mode 100644 UnitTests~/Animation/MMD/MMDMode_Reactive.prefab.meta create mode 100644 UnitTests~/Animation/MMD/Overrides.controller create mode 100644 UnitTests~/Animation/MMD/Overrides.controller.meta create mode 100644 UnitTests~/Animation/MMD/empty.anim create mode 100644 UnitTests~/Animation/MMD/empty.anim.meta create mode 100644 docs~/docs/general-behavior/index.md create mode 100644 docs~/docs/general-behavior/mmd.md create mode 100644 docs~/docs/reference/mmd-layer-control.md create mode 100644 docs~/i18n/ja/docusaurus-plugin-content-docs/current/general-behavior/index.md create mode 100644 docs~/i18n/ja/docusaurus-plugin-content-docs/current/general-behavior/mmd.md create mode 100644 docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/mmd-layer-control.md diff --git a/.github/ProjectRoot/vpm-manifest-2022.json b/.github/ProjectRoot/vpm-manifest-2022.json index dc8f31ff..3bc2f8cc 100644 --- a/.github/ProjectRoot/vpm-manifest-2022.json +++ b/.github/ProjectRoot/vpm-manifest-2022.json @@ -4,7 +4,7 @@ "version": "3.7.4" }, "nadena.dev.ndmf": { - "version": "1.7.0-alpha.3" + "version": "1.7.0-alpha.4" } }, "locked": { @@ -19,7 +19,7 @@ "dependencies": {} }, "nadena.dev.ndmf": { - "version": "1.7.0-alpha.3" + "version": "1.7.0-alpha.4" } } } \ No newline at end of file diff --git a/CHANGELOG-PRERELEASE-jp.md b/CHANGELOG-PRERELEASE-jp.md index 3fc0c1c7..6291002b 100644 --- a/CHANGELOG-PRERELEASE-jp.md +++ b/CHANGELOG-PRERELEASE-jp.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1497] CHANGELOGをドキュメンテーションサイトに追加 - [#1482] `Merge Animator` に既存のアニメーターコントローラーを置き換える機能を追加 - [#1481] [World Scale Object](https://m-a.nadena.dev/dev/ja/docs/reference/world-scale-object)を追加 +- [#1489] [`MA MMD Layer Control`](https://modular-avatar.nadena.dev/docs/general-behavior/mmd)を追加 ### Fixed - [#1492] 前回のプレリリースでアイコンとロゴアセットが間違っていた問題を修正 @@ -20,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0  に対してはWrite Defaultsを調整しないように変更。 - [#1429] Merge Armature は、特定の場合にPhysBoneに指定されたヒューマノイドボーンをマージできるようになりました。 - 具体的には、子ヒューマノイドボーンがある場合はPhysBoneから除外される必要があります。 +- [#1489] `Merge Blend Tree` やリアクティブコンポーネントとMMDワールドの互換性の問題を修正。 + 詳細は[ドキュメント](https://modular-avatar.nadena.dev/docs/general-behavior/mmd)を参照してください。 ### Removed diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 195dca50..bd6266cb 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -11,9 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1497] Added changelog to docs site - [#1482] Added support for replacing pre-existing animator controllers to `Merge Animator` - [#1481] Added [World Scale Object](https://m-a.nadena.dev/dev/docs/reference/world-scale-object) +- [#1489] Added [`MA MMD Layer Control`](https://modular-avatar.nadena.dev/docs/general-behavior/mmd) ### Fixed - [#1492] Fixed incorrect icon and logo assets in prior prerelease +- [#1489] Fixed compatibility issues between `Merge Blend Tree` or reactive components and MMD worlds. + See [documentation](https://modular-avatar.nadena.dev/docs/general-behavior/mmd) for details on the new handling. ### Changed - [#1483] The Merge Animator "Match Avatar Write Defaults" option will no longer adjust write defaults on states in diff --git a/CHANGELOG-jp.md b/CHANGELOG-jp.md index 88630cf3..15189c32 100644 --- a/CHANGELOG-jp.md +++ b/CHANGELOG-jp.md @@ -12,10 +12,13 @@ Modular Avatarの主な変更点をこのファイルで記録しています。 - CHANGELOGファイルを追加 - [#1482] `Merge Animator` に既存のアニメーターコントローラーを置き換える機能を追加 - [#1481] [World Scale Object](https://m-a.nadena.dev/ja/docs/reference/world-scale-object)を追加 +- [#1489] [`MA MMD Layer Control`](https://modular-avatar.nadena.dev/docs/general-behavior/mmd)を追加 ### Fixed - [#1460] パラメーターアセットをMA Parametersにインポートするとき、ローカルのみのパラメーターが間違ってアニメーターのみ扱いになる問題を修正 - +- [#1489] `Merge Blend Tree` やリアクティブコンポーネントとMMDワールドの互換性の問題を修正。 + 詳細は[ドキュメント](https://modular-avatar.nadena.dev/docs/general-behavior/mmd)を参照してください。 + ### Changed - [#1476] ModularAvatarMergeAnimator と ModularAvatarMergeParameter を新しい NDMF API (`IVirtualizeMotion` と `IVirtualizeAnimatorController`) を使用するように変更 - [#1483] Merge Animator の 「アバターの Write Defaults 設定に合わせる」設定では、Additiveなレイヤー、および単一Stateかつ遷移のないレイヤー diff --git a/CHANGELOG.md b/CHANGELOG.md index bc06f511..ae4d1ab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added CHANGELOG files - [#1482] Added support for replacing pre-existing animator controllers to `Merge Animator` - [#1481] Added [World Scale Object](https://m-a.nadena.dev/docs/reference/world-scale-object) +- [#1489] Added [`MA MMD Layer Control`](https://modular-avatar.nadena.dev/docs/general-behavior/mmd) ### Fixed - [#1460] When importing parameter assets in MA Parameters, "local only" parameters were incorrectly treated as "animator only" +- [#1489] Fixed compatibility issues between `Merge Blend Tree` or reactive components and MMD worlds. + See [documentation](https://modular-avatar.nadena.dev/docs/general-behavior/mmd) for details on the new handling. ### Changed - [#1476] Switch ModularAvatarMergeAnimator and ModularAvatarMergeParameter to use new NDMF APIs (`IVirtualizeMotion` and `IVirtualizeAnimatorController`) diff --git a/Editor/Animation/MMDRelayPass.cs b/Editor/Animation/MMDRelayPass.cs new file mode 100644 index 00000000..d52dc580 --- /dev/null +++ b/Editor/Animation/MMDRelayPass.cs @@ -0,0 +1,234 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf; +using nadena.dev.ndmf.animator; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using VRC.SDKBase; +using BuildContext = nadena.dev.ndmf.BuildContext; +using Object = UnityEngine.Object; + +namespace nadena.dev.modular_avatar.animation +{ + internal class MMDRelayState + { + internal HashSet mmdAffectedOriginalLayers = new(); + } + + internal class MMDRelayEarlyPass : Pass + { + protected override void Execute(BuildContext context) + { + var asc = context.Extension(); + if (asc.ControllerContext.Controllers.TryGetValue(VRCAvatarDescriptor.AnimLayerType.FX, out var fx)) + { + context.GetState().mmdAffectedOriginalLayers = new HashSet( + fx.Layers.Skip(1).Take(2) + ); + } + } + } + + /// + /// Many MMD worlds animate the first three FX layers to weight zero. When MA injects new layers, this can hit + /// unintended layers (eg the RC base state layer). + /// To work around this, we'll inject a layer which will relay its active state into a parameter; then, we add a + /// layer to relay this to layers which should be affected. Finally, any layer which _shouldn't_ be affected is + /// pushed out of the first three layers by injecting dummy layers. + /// + internal class MMDRelayPass : Pass + { + private const string MMDRelayParam = "__MA/Internal/MMDNotActive"; + internal const string ControlLayerName = "Modular Avatar: MMD Control"; + internal const string DummyLayerName = "Modular Avatar: MMD Dummy"; + internal const string StateNameInitial = "Initial"; + internal const string StateNameNotMMD = "NotMMD"; + internal const string StateNameMMD = "MMD"; + + protected override void Execute(BuildContext context) + { + var asc = context.Extension(); + if (!asc.ControllerContext.Controllers.TryGetValue(VRCAvatarDescriptor.AnimLayerType.FX, out var fx)) + return; + + var affectedLayers = context.GetState().mmdAffectedOriginalLayers; + + foreach (var layer in fx.Layers) + { + var rootMMDModeBehaviors = layer.StateMachine.Behaviours + .OfType() + .ToList(); + + if (rootMMDModeBehaviors.Count == 0) continue; + if (rootMMDModeBehaviors.Count > 1) + { + ErrorReport.ReportError(Localization.L, ErrorSeverity.Error, + "error.mmd.multiple_mmd_mode_behaviors", layer.Name); + continue; + } + + if (rootMMDModeBehaviors[0].DisableInMMDMode) + { + affectedLayers.Add(layer); + } + else + { + affectedLayers.Remove(layer); + } + + layer.StateMachine.Behaviours = layer.StateMachine.Behaviours + .Where(b => b is not ModularAvatarMMDLayerControl).ToImmutableList(); + Object.DestroyImmediate(rootMMDModeBehaviors[0]); + + // check for child behaviors + // TODO: implement filtering on AllReachableNodes + foreach (var node in layer.AllReachableNodes()) + { + if (node is VirtualState state) + { + if (state.Behaviours.Any(b => b is ModularAvatarMMDLayerControl)) + { + ErrorReport.ReportError(Localization.L, ErrorSeverity.Error, + "error.mmd.mmd_mode_in_child_state", layer.Name, state.Name); + } + } + else if (node is VirtualStateMachine vsm) + { + if (vsm.Behaviours.Any(b => b is ModularAvatarMMDLayerControl)) + { + ErrorReport.ReportError(Localization.L, ErrorSeverity.Error, + "error.mmd.mmd_mode_in_child_state_machine", layer.Name, vsm.Name); + } + } + } + } + + var needsAdjustment = fx.Layers.Select((layer, index) => (layer, index)) + .Any(pair => affectedLayers.Contains(pair.layer) != (pair.index < 3 && pair.index != 0)); + if (!needsAdjustment) return; + + var toDisable = fx.Layers.Where(l => affectedLayers.Contains(l)) + .Select(l => l.VirtualLayerIndex) + .ToList(); + + fx.Parameters = fx.Parameters.Add(MMDRelayParam, new AnimatorControllerParameter + { + name = MMDRelayParam, + type = AnimatorControllerParameterType.Float, + defaultFloat = 0 + }); + + var currentLayers = fx.Layers.ToList(); + var newLayers = new List(); + + // Layer zero's weight can't be changed anyway, so leave it where it is. + newLayers.Add(currentLayers[0]); + currentLayers.RemoveAt(0); + newLayers.Add(CreateMMDLayer(fx, toDisable)); + + // Add a dummy layer + var dummy = fx.AddLayer(new LayerPriority(0), DummyLayerName); + var s = dummy.StateMachine!.DefaultState = dummy.StateMachine.AddState("Dummy"); + s.Motion = VirtualClip.Create("empty"); + newLayers.Add(dummy); + + fx.Layers = newLayers.Concat(currentLayers); + } + + private static VirtualLayer CreateMMDLayer(VirtualAnimatorController fx, List virtualLayers) + { + // We'll reorder this later, so the layer priority doesn't matter + var mmdControl = fx.AddLayer(new LayerPriority(0), ControlLayerName); + var stateMachine = mmdControl.StateMachine ?? throw new Exception("No state machine on MMD Control layer"); + + var motion = VirtualClip.Create("MMDRelay"); + motion.SetFloatCurve(EditorCurveBinding.FloatCurve("", typeof(Animator), MMDRelayParam), + AnimationCurve.Constant(0, 1, 1) + ); + + var state_initial = stateMachine.AddState(StateNameInitial); + state_initial.Motion = motion; + + var state_notmmd = stateMachine.AddState(StateNameNotMMD); + state_notmmd.Motion = motion; + + var state_mmd = stateMachine.AddState(StateNameMMD); + state_mmd.Motion = motion; + + var t = VirtualStateTransition.Create(); + t.SetDestination(state_mmd); + t.Conditions = ImmutableList.Create(new AnimatorCondition + { + mode = AnimatorConditionMode.Less, + parameter = MMDRelayParam, + threshold = 0.5f + }); + + state_notmmd.Transitions = ImmutableList.Create(t); + + t = VirtualStateTransition.Create(); + t.SetDestination(state_notmmd); + t.Conditions = ImmutableList.Create(new AnimatorCondition + { + mode = AnimatorConditionMode.Greater, + parameter = MMDRelayParam, + threshold = 0.5f + }); + + state_mmd.Transitions = ImmutableList.Create(t); + + t = VirtualStateTransition.Create(); + t.SetDestination(state_mmd); + t.Conditions = ImmutableList.Create(new AnimatorCondition + { + mode = AnimatorConditionMode.Less, + parameter = MMDRelayParam, + threshold = 0.5f + }); + + state_initial.Transitions = ImmutableList.Create(t); + + stateMachine.DefaultState = state_initial; + + var mmd_behaviors = ImmutableList.CreateBuilder(); + var notmmd_behaviors = ImmutableList.CreateBuilder(); + + foreach (var index in virtualLayers) + { + var behavior = ScriptableObject.CreateInstance(); + behavior.layer = index; + behavior.playable = VRC_AnimatorLayerControl.BlendableLayer.FX; + behavior.goalWeight = 0; + behavior.blendDuration = 0; + + mmd_behaviors.Add(behavior); + + behavior = ScriptableObject.CreateInstance(); + behavior.layer = index; + behavior.playable = VRC_AnimatorLayerControl.BlendableLayer.FX; + behavior.goalWeight = 1; + behavior.blendDuration = 0; + + notmmd_behaviors.Add(behavior); + } + + state_notmmd.Behaviours = notmmd_behaviors.ToImmutable(); + state_mmd.Behaviours = mmd_behaviors.ToImmutable(); + + return mmdControl; + } + + internal static bool IsRelayLayer(string layerName) + { + return layerName == ControlLayerName || layerName == DummyLayerName; + } + } +} \ No newline at end of file diff --git a/Editor/Animation/MMDRelayPass.cs.meta b/Editor/Animation/MMDRelayPass.cs.meta new file mode 100644 index 00000000..ccdf8903 --- /dev/null +++ b/Editor/Animation/MMDRelayPass.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 163fd3d0edea43d5969395079f561986 +timeCreated: 1741745889 \ No newline at end of file diff --git a/Editor/Inspector/MMDModeEditor.cs b/Editor/Inspector/MMDModeEditor.cs new file mode 100644 index 00000000..b00f9546 --- /dev/null +++ b/Editor/Inspector/MMDModeEditor.cs @@ -0,0 +1,30 @@ +using UnityEditor; +using static nadena.dev.modular_avatar.core.editor.Localization; + +namespace nadena.dev.modular_avatar.core.editor +{ + [CustomEditor(typeof(ModularAvatarMMDLayerControl))] + internal class MMDModeEditor : MAEditorBase + { + private SerializedProperty m_p_DisableInMMDMode; + + private void OnEnable() + { + m_p_DisableInMMDMode = + serializedObject.FindProperty(nameof(ModularAvatarMMDLayerControl.m_DisableInMMDMode)); + } + + protected override void OnInnerInspectorGUI() + { + serializedObject.Update(); + + LogoDisplay.DisplayLogo(); + + EditorGUILayout.PropertyField(m_p_DisableInMMDMode, G("mmd_mode.disable_in_mmd_mode")); + + ShowLanguageUI(); + + serializedObject.ApplyModifiedProperties(); + } + } +} \ No newline at end of file diff --git a/Editor/Inspector/MMDModeEditor.cs.meta b/Editor/Inspector/MMDModeEditor.cs.meta new file mode 100644 index 00000000..c775074b --- /dev/null +++ b/Editor/Inspector/MMDModeEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a1a682db3a3b491fa27980adfeeacffd +timeCreated: 1741836147 \ No newline at end of file diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index b951fe19..578087e3 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -54,6 +54,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin seq.WithRequiredExtension(typeof(AnimatorServicesContext), _s2 => { #if MA_VRCSDK3_AVATARS + seq.Run(MMDRelayEarlyPass.Instance); seq.Run(RenameParametersPluginPass.Instance); seq.Run(ParameterAssignerPass.Instance); seq.Run(MergeBlendTreePass.Instance); @@ -98,6 +99,8 @@ namespace nadena.dev.modular_avatar.core.editor.plugin ctx => { ctx.Extension().RemoveEmptyLayers(); }); seq.Run("Harmonize animator parameter types", ctx => { ctx.Extension().HarmonizeParameterTypes(); }); + + seq.Run(MMDRelayPass.Instance); }); #if MA_VRCSDK3_AVATARS seq.Run(PhysbonesBlockerPluginPass.Instance); diff --git a/Runtime/ModularAvatarMMDLayerControl.cs b/Runtime/ModularAvatarMMDLayerControl.cs new file mode 100644 index 00000000..bca66611 --- /dev/null +++ b/Runtime/ModularAvatarMMDLayerControl.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using UnityEngine; + +namespace nadena.dev.modular_avatar.core +{ + [AddComponentMenu("Modular Avatar/MA MMD Layer Control")] + [DisallowMultipleComponent] + [HelpURL("https://modular-avatar.nadena.dev/docs/reference/mmd-layer-control?lang=auto")] + [SuppressMessage("ReSharper", "InconsistentNaming")] + // ReSharper disable once RequiredBaseTypesIsNotInherited (false positive) + public sealed class ModularAvatarMMDLayerControl : StateMachineBehaviour + { + [SerializeField] internal bool m_DisableInMMDMode; + + [PublicAPI] + public bool DisableInMMDMode + { + get => m_DisableInMMDMode; + set => m_DisableInMMDMode = value; + } + } +} \ No newline at end of file diff --git a/Runtime/ModularAvatarMMDLayerControl.cs.meta b/Runtime/ModularAvatarMMDLayerControl.cs.meta new file mode 100644 index 00000000..1bbd7ccb --- /dev/null +++ b/Runtime/ModularAvatarMMDLayerControl.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d1d979d3cedd4ddd969f414e2ea04fb8 +timeCreated: 1741836107 \ No newline at end of file diff --git a/UnitTests~/Animation/LayerPruningTest.cs b/UnitTests~/Animation/LayerPruningTest.cs index 40cd179c..078edca5 100644 --- a/UnitTests~/Animation/LayerPruningTest.cs +++ b/UnitTests~/Animation/LayerPruningTest.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using modular_avatar_tests; +using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core.editor; using NUnit.Framework; using UnityEditor.Animations; @@ -21,10 +22,12 @@ namespace modular_avatar_tests var fxController = (AnimatorController) FindController(prefab, VRCAvatarDescriptor.AnimLayerType.FX).animatorController; var l0 = fxController.layers[0]; - var l1 = fxController.layers[1]; - var l2 = fxController.layers[2]; - var l3 = fxController.layers[3]; - var l3a = fxController.layers[4]; + Assert.AreEqual(MMDRelayPass.ControlLayerName, fxController.layers[1].name); + Assert.AreEqual(MMDRelayPass.DummyLayerName, fxController.layers[2].name); + var l1 = fxController.layers[3]; + var l2 = fxController.layers[4]; + var l3 = fxController.layers[5]; + var l3a = fxController.layers[6]; Assert.AreEqual("Base Layer", l0.name); Assert.AreEqual("L1", l1.name); @@ -37,10 +40,10 @@ namespace modular_avatar_tests Assert.AreEqual("2", ((VRCAnimatorLayerControl)l3.stateMachine.defaultState.behaviours[0]).debugString); Assert.IsTrue(l3.stateMachine.defaultState.behaviours[1] is VRCAnimatorTrackingControl); Assert.AreEqual("3", ((VRCAnimatorLayerControl)l3.stateMachine.defaultState.behaviours[2]).debugString); - Assert.AreEqual(3, ((VRCAnimatorLayerControl)l3.stateMachine.defaultState.behaviours[0]).layer); + Assert.AreEqual(FindFxLayerIndex(prefab, l3), ((VRCAnimatorLayerControl)l3.stateMachine.defaultState.behaviours[0]).layer); Assert.AreEqual(1, l3a.stateMachine.defaultState.behaviours.Length); - Assert.AreEqual(3, ((VRCAnimatorLayerControl)l3a.stateMachine.defaultState.behaviours[0]).layer); + Assert.AreEqual(FindFxLayerIndex(prefab, l3), ((VRCAnimatorLayerControl)l3a.stateMachine.defaultState.behaviours[0]).layer); } } } diff --git a/UnitTests~/Animation/MMD.meta b/UnitTests~/Animation/MMD.meta new file mode 100644 index 00000000..5c8c52f8 --- /dev/null +++ b/UnitTests~/Animation/MMD.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 046888971cba42e895a515d3d07b955b +timeCreated: 1742001943 \ No newline at end of file diff --git a/UnitTests~/Animation/MMD/AC1.controller b/UnitTests~/Animation/MMD/AC1.controller new file mode 100644 index 00000000..ec943823 --- /dev/null +++ b/UnitTests~/Animation/MMD/AC1.controller @@ -0,0 +1,192 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-7355338869790508137 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &-3477861051435458144 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: L0 + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -7355338869790508137} + m_Position: {x: 441.0094, y: 127.91522, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: -7355338869790508137} +--- !u!1107 &-3163258767259997666 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: L2 + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 2165750007709086016} + m_Position: {x: 320, y: 130, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 2165750007709086016} +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AC1 + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: L0 + m_StateMachine: {fileID: -3477861051435458144} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 0 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: L1 + m_StateMachine: {fileID: 3734055781436131242} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 1 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: L2 + m_StateMachine: {fileID: -3163258767259997666} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 1 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} +--- !u!1102 &2165750007709086016 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1102 &3470771388114793831 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &3734055781436131242 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: L1 + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 3470771388114793831} + m_Position: {x: 495.48987, y: 46.194702, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 3470771388114793831} diff --git a/UnitTests~/Animation/MMD/AC1.controller.meta b/UnitTests~/Animation/MMD/AC1.controller.meta new file mode 100644 index 00000000..1127e0eb --- /dev/null +++ b/UnitTests~/Animation/MMD/AC1.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e7f6321e9d2601a45a3efa0a24305b78 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/Animation/MMD/AC2.controller b/UnitTests~/Animation/MMD/AC2.controller new file mode 100644 index 00000000..a41ca7de --- /dev/null +++ b/UnitTests~/Animation/MMD/AC2.controller @@ -0,0 +1,192 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-7355338869790508137 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &-3477861051435458144 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: M0 + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -7355338869790508137} + m_Position: {x: 441.0094, y: 127.91522, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: -7355338869790508137} +--- !u!1107 &-3163258767259997666 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: M2 + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 2165750007709086016} + m_Position: {x: 320, y: 130, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 2165750007709086016} +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AC2 + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: M0 + m_StateMachine: {fileID: -3477861051435458144} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 0 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: M1 + m_StateMachine: {fileID: 3734055781436131242} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 1 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: M2 + m_StateMachine: {fileID: -3163258767259997666} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 1 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} +--- !u!1102 &2165750007709086016 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1102 &3470771388114793831 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &3734055781436131242 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: M1 + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 3470771388114793831} + m_Position: {x: 495.48987, y: 46.194702, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 3470771388114793831} diff --git a/UnitTests~/Animation/MMD/AC2.controller.meta b/UnitTests~/Animation/MMD/AC2.controller.meta new file mode 100644 index 00000000..b5acc9f8 --- /dev/null +++ b/UnitTests~/Animation/MMD/AC2.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bf8dbf58ea7a7544cb5c7f86b790d0ff +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/Animation/MMD/MMDHandlingTests.cs b/UnitTests~/Animation/MMD/MMDHandlingTests.cs new file mode 100644 index 00000000..390a96c5 --- /dev/null +++ b/UnitTests~/Animation/MMD/MMDHandlingTests.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using nadena.dev.modular_avatar.animation; +using nadena.dev.modular_avatar.core.editor; +using NUnit.Framework; +using UnityEditor.Animations; +using UnityEngine.Assertions.Must; +using VRC.SDK3.Avatars.Components; +using VRC.SDKBase; + +namespace modular_avatar_tests.MMD +{ + public class MMDHandlingTests : TestBase + { + [Test] + public void MMDMode_NoopHandling() + { + var prefab = CreatePrefab("MMDMode_Noop.prefab"); + + AvatarProcessor.ProcessAvatar(prefab); + + var fx = FindFxController(prefab); + var fxc = (AnimatorController)fx.animatorController; + + Assert.AreEqual(3, fxc.layers.Length); + Assert.AreEqual("L0", fxc.layers[0].name); + Assert.AreEqual("L1", fxc.layers[1].name); + Assert.AreEqual("L2", fxc.layers[2].name); + } + + [Test] + public void MMDMode_ReactiveComponent() + { + var prefab = CreatePrefab("MMDMode_Reactive.prefab"); + + AvatarProcessor.ProcessAvatar(prefab); + + var fx = FindFxController(prefab); + var fxc = (AnimatorController)fx.animatorController; + + // RC, MMD, dummy, L0, L1, L2 + AssertMMDModeHandling(fxc, 4, 5); + + Assert.AreEqual(MergeBlendTreePass.BlendTreeLayerName, fxc.layers[0].name); + Assert.AreEqual(MMDRelayPass.ControlLayerName, fxc.layers[1].name); + Assert.AreEqual(MMDRelayPass.DummyLayerName, fxc.layers[2].name); + Assert.AreEqual("L0", fxc.layers[3].name); + Assert.AreEqual("L1", fxc.layers[4].name); + Assert.AreEqual("L2", fxc.layers[5].name); + } + + [Test] + public void MMDMode_MergeBefore() + { + var prefab = CreatePrefab("MMDMode_MergeBefore.prefab"); + + AvatarProcessor.ProcessAvatar(prefab); + + var fx = FindFxController(prefab); + var fxc = (AnimatorController)fx.animatorController; + + // M0, MMD, dummy, M1, M2, L0, L1, L2 + AssertMMDModeHandling(fxc, 6, 7); + + Assert.AreEqual(8, fxc.layers.Length); + Assert.AreEqual("M0", fxc.layers[0].name); + Assert.AreEqual(MMDRelayPass.ControlLayerName, fxc.layers[1].name); + Assert.AreEqual(MMDRelayPass.DummyLayerName, fxc.layers[2].name); + Assert.AreEqual("M1", fxc.layers[3].name); + Assert.AreEqual("M2", fxc.layers[4].name); + Assert.AreEqual("L0", fxc.layers[5].name); + Assert.AreEqual("L1", fxc.layers[6].name); + Assert.AreEqual("L2", fxc.layers[7].name); + } + + [Test] + public void MMDMode_ManualOverride() + { + var prefab = CreatePrefab("MMDMode_Overrides.prefab"); + + AvatarProcessor.ProcessAvatar(prefab); + + var fx = FindFxController(prefab); + var fxc = (AnimatorController)fx.animatorController; + + // Base, MMD, dummy, ForceOff, DefaultOn, DefaultOff, ForceOn + AssertMMDModeHandling(fxc, 4, 6); + } + + + private void AssertMMDModeHandling(AnimatorController fxc, params int[] layers) + { + Assert.AreEqual(MMDRelayPass.ControlLayerName, fxc.layers[1].name); + Assert.AreEqual(MMDRelayPass.DummyLayerName, fxc.layers[2].name); + + var expectedLayers = new HashSet(layers); + + foreach (var state in fxc.layers[1].stateMachine.states) + { + var actualLayers = new HashSet(); + float expectedWeight = -1f; + + var behaviors = state.state.behaviours; + + switch (state.state.name) + { + case MMDRelayPass.StateNameInitial: + Assert.IsEmpty(behaviors); + Assert.AreEqual(fxc.layers[1].stateMachine.defaultState, state.state); + continue; + + case MMDRelayPass.StateNameNotMMD: + expectedWeight = 1f; + break; + + case MMDRelayPass.StateNameMMD: + expectedWeight = 0f; + break; + + default: + Assert.Fail($"Unexpected state {state.state.name}"); + break; + } + + foreach (var behavior in state.state.behaviours) + { + if (behavior is VRCAnimatorLayerControl lc) + { + Assert.AreEqual(expectedWeight, lc.goalWeight); + Assert.AreEqual(VRC_AnimatorLayerControl.BlendableLayer.FX, lc.playable); + Assert.IsTrue(actualLayers.Add(lc.layer)); + } + } + + Assert.That(expectedLayers, Is.EquivalentTo(actualLayers)); + } + } + } +} \ No newline at end of file diff --git a/UnitTests~/Animation/MMD/MMDHandlingTests.cs.meta b/UnitTests~/Animation/MMD/MMDHandlingTests.cs.meta new file mode 100644 index 00000000..30d644fe --- /dev/null +++ b/UnitTests~/Animation/MMD/MMDHandlingTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 298d11b5361148b499d196aabcaaab73 +timeCreated: 1742002556 \ No newline at end of file diff --git a/UnitTests~/Animation/MMD/MMDMode_MergeBefore.prefab b/UnitTests~/Animation/MMD/MMDMode_MergeBefore.prefab new file mode 100644 index 00000000..56bc64fb --- /dev/null +++ b/UnitTests~/Animation/MMD/MMDMode_MergeBefore.prefab @@ -0,0 +1,380 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &784701382543359949 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8612238306248833727} + - component: {fileID: 3495771561595279164} + - component: {fileID: 1229959924919852631} + - component: {fileID: 6320176121826827791} + m_Layer: 0 + m_Name: MMDMode_MergeBefore + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8612238306248833727 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 784701382543359949} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.02920363, y: 0.5853253, z: -0.39815798} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7556123215267729845} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &3495771561595279164 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 784701382543359949} + m_Enabled: 1 + m_Avatar: {fileID: 0} + m_Controller: {fileID: 0} + m_CullingMode: 0 + m_UpdateMode: 0 + m_ApplyRootMotion: 0 + m_LinearVelocityBlending: 0 + m_StabilizeFeet: 0 + m_WarningMessage: + m_HasTransformHierarchy: 1 + m_AllowConstantClipSamplingOptimization: 1 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 +--- !u!114 &1229959924919852631 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 784701382543359949} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 542108242, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3} + m_Name: + m_EditorClassIdentifier: + Name: + ViewPosition: {x: 0, y: 1.6, z: 0.2} + Animations: 0 + ScaleIPD: 1 + lipSync: 0 + lipSyncJawBone: {fileID: 0} + lipSyncJawClosed: {x: 0, y: 0, z: 0, w: 1} + lipSyncJawOpen: {x: 0, y: 0, z: 0, w: 1} + VisemeSkinnedMesh: {fileID: 0} + MouthOpenBlendShapeName: Facial_Blends.Jaw_Down + VisemeBlendShapes: [] + unityVersion: + portraitCameraPositionOffset: {x: 0, y: 0, z: 0} + portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139} + networkIDs: [] + customExpressions: 1 + expressionsMenu: {fileID: 0} + expressionParameters: {fileID: 0} + enableEyeLook: 0 + customEyeLookSettings: + eyeMovement: + confidence: 0.5 + excitement: 0.5 + leftEye: {fileID: 0} + rightEye: {fileID: 0} + eyesLookingStraight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingUp: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingDown: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingLeft: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingRight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidType: 0 + upperLeftEyelid: {fileID: 0} + upperRightEyelid: {fileID: 0} + lowerLeftEyelid: {fileID: 0} + lowerRightEyelid: {fileID: 0} + eyelidsDefault: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsClosed: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingUp: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingDown: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsSkinnedMesh: {fileID: 0} + eyelidsBlendshapes: + customizeAnimationLayers: 1 + baseAnimationLayers: + - isEnabled: 0 + type: 0 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 4 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 5 + animatorController: {fileID: 9100000, guid: e7f6321e9d2601a45a3efa0a24305b78, + type: 2} + mask: {fileID: 0} + isDefault: 0 + specialAnimationLayers: + - isEnabled: 0 + type: 6 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 7 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 8 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + AnimationPreset: {fileID: 0} + animationHashSet: [] + autoFootsteps: 1 + autoLocomotion: 1 + collider_head: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_torso: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} +--- !u!114 &6320176121826827791 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 784701382543359949} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + m_Name: + m_EditorClassIdentifier: + launchedFromSDKPipeline: 0 + completedSDKPipeline: 0 + blueprintId: + contentType: 0 + assetBundleUnityVersion: + fallbackStatus: 0 +--- !u!1 &8032631290352466631 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7556123215267729845} + - component: {fileID: 2259268676535659146} + m_Layer: 0 + m_Name: mergeBefore + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7556123215267729845 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8032631290352466631} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8612238306248833727} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &2259268676535659146 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8032631290352466631} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} + m_Name: + m_EditorClassIdentifier: + animator: {fileID: 9100000, guid: bf8dbf58ea7a7544cb5c7f86b790d0ff, type: 2} + layerType: 5 + deleteAttachedAnimator: 1 + pathMode: 0 + matchAvatarWriteDefaults: 0 + relativePathRoot: + referencePath: + targetObject: {fileID: 0} + layerPriority: -1 + mergeAnimatorMode: 0 diff --git a/UnitTests~/Animation/MMD/MMDMode_MergeBefore.prefab.meta b/UnitTests~/Animation/MMD/MMDMode_MergeBefore.prefab.meta new file mode 100644 index 00000000..e38410c2 --- /dev/null +++ b/UnitTests~/Animation/MMD/MMDMode_MergeBefore.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5cd01837b01aa8b4897906d81b421b32 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/Animation/MMD/MMDMode_Noop.prefab b/UnitTests~/Animation/MMD/MMDMode_Noop.prefab new file mode 100644 index 00000000..20ac55d5 --- /dev/null +++ b/UnitTests~/Animation/MMD/MMDMode_Noop.prefab @@ -0,0 +1,325 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &252931896825782499 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5142404049502950569} + - component: {fileID: 5869130456277801080} + - component: {fileID: 9098796828299356927} + - component: {fileID: 5312873765374103901} + m_Layer: 0 + m_Name: MMDMode_Noop + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5142404049502950569 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 252931896825782499} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.02920363, y: 0.5853253, z: -0.39815798} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &5869130456277801080 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 252931896825782499} + m_Enabled: 1 + m_Avatar: {fileID: 0} + m_Controller: {fileID: 0} + m_CullingMode: 0 + m_UpdateMode: 0 + m_ApplyRootMotion: 0 + m_LinearVelocityBlending: 0 + m_StabilizeFeet: 0 + m_WarningMessage: + m_HasTransformHierarchy: 1 + m_AllowConstantClipSamplingOptimization: 1 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 +--- !u!114 &9098796828299356927 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 252931896825782499} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 542108242, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3} + m_Name: + m_EditorClassIdentifier: + Name: + ViewPosition: {x: 0, y: 1.6, z: 0.2} + Animations: 0 + ScaleIPD: 1 + lipSync: 0 + lipSyncJawBone: {fileID: 0} + lipSyncJawClosed: {x: 0, y: 0, z: 0, w: 1} + lipSyncJawOpen: {x: 0, y: 0, z: 0, w: 1} + VisemeSkinnedMesh: {fileID: 0} + MouthOpenBlendShapeName: Facial_Blends.Jaw_Down + VisemeBlendShapes: [] + unityVersion: + portraitCameraPositionOffset: {x: 0, y: 0, z: 0} + portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139} + networkIDs: [] + customExpressions: 1 + expressionsMenu: {fileID: 0} + expressionParameters: {fileID: 0} + enableEyeLook: 0 + customEyeLookSettings: + eyeMovement: + confidence: 0.5 + excitement: 0.5 + leftEye: {fileID: 0} + rightEye: {fileID: 0} + eyesLookingStraight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingUp: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingDown: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingLeft: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingRight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidType: 0 + upperLeftEyelid: {fileID: 0} + upperRightEyelid: {fileID: 0} + lowerLeftEyelid: {fileID: 0} + lowerRightEyelid: {fileID: 0} + eyelidsDefault: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsClosed: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingUp: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingDown: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsSkinnedMesh: {fileID: 0} + eyelidsBlendshapes: + customizeAnimationLayers: 1 + baseAnimationLayers: + - isEnabled: 0 + type: 0 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 4 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 5 + animatorController: {fileID: 9100000, guid: e7f6321e9d2601a45a3efa0a24305b78, + type: 2} + mask: {fileID: 0} + isDefault: 0 + specialAnimationLayers: + - isEnabled: 0 + type: 6 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 7 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 8 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + AnimationPreset: {fileID: 0} + animationHashSet: [] + autoFootsteps: 1 + autoLocomotion: 1 + collider_head: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_torso: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} +--- !u!114 &5312873765374103901 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 252931896825782499} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + m_Name: + m_EditorClassIdentifier: + launchedFromSDKPipeline: 0 + completedSDKPipeline: 0 + blueprintId: + contentType: 0 + assetBundleUnityVersion: + fallbackStatus: 0 diff --git a/UnitTests~/Animation/MMD/MMDMode_Noop.prefab.meta b/UnitTests~/Animation/MMD/MMDMode_Noop.prefab.meta new file mode 100644 index 00000000..187b621d --- /dev/null +++ b/UnitTests~/Animation/MMD/MMDMode_Noop.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9467ce2e34cfbc74aa9cb6ed1b990488 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/Animation/MMD/MMDMode_Overrides.prefab b/UnitTests~/Animation/MMD/MMDMode_Overrides.prefab new file mode 100644 index 00000000..4481c3d7 --- /dev/null +++ b/UnitTests~/Animation/MMD/MMDMode_Overrides.prefab @@ -0,0 +1,325 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &3649483952032229877 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3671950317217900310} + - component: {fileID: 5516575333733947756} + - component: {fileID: 3355915580969342691} + - component: {fileID: 8742850335181783537} + m_Layer: 0 + m_Name: MMDMode_Overrides + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3671950317217900310 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3649483952032229877} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.02920363, y: 0.5853253, z: -0.39815798} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &5516575333733947756 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3649483952032229877} + m_Enabled: 1 + m_Avatar: {fileID: 0} + m_Controller: {fileID: 0} + m_CullingMode: 0 + m_UpdateMode: 0 + m_ApplyRootMotion: 0 + m_LinearVelocityBlending: 0 + m_StabilizeFeet: 0 + m_WarningMessage: + m_HasTransformHierarchy: 1 + m_AllowConstantClipSamplingOptimization: 1 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 +--- !u!114 &3355915580969342691 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3649483952032229877} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 542108242, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3} + m_Name: + m_EditorClassIdentifier: + Name: + ViewPosition: {x: 0, y: 1.6, z: 0.2} + Animations: 0 + ScaleIPD: 1 + lipSync: 0 + lipSyncJawBone: {fileID: 0} + lipSyncJawClosed: {x: 0, y: 0, z: 0, w: 1} + lipSyncJawOpen: {x: 0, y: 0, z: 0, w: 1} + VisemeSkinnedMesh: {fileID: 0} + MouthOpenBlendShapeName: Facial_Blends.Jaw_Down + VisemeBlendShapes: [] + unityVersion: + portraitCameraPositionOffset: {x: 0, y: 0, z: 0} + portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139} + networkIDs: [] + customExpressions: 1 + expressionsMenu: {fileID: 0} + expressionParameters: {fileID: 0} + enableEyeLook: 0 + customEyeLookSettings: + eyeMovement: + confidence: 0.5 + excitement: 0.5 + leftEye: {fileID: 0} + rightEye: {fileID: 0} + eyesLookingStraight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingUp: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingDown: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingLeft: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingRight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidType: 0 + upperLeftEyelid: {fileID: 0} + upperRightEyelid: {fileID: 0} + lowerLeftEyelid: {fileID: 0} + lowerRightEyelid: {fileID: 0} + eyelidsDefault: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsClosed: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingUp: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingDown: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsSkinnedMesh: {fileID: 0} + eyelidsBlendshapes: + customizeAnimationLayers: 1 + baseAnimationLayers: + - isEnabled: 0 + type: 0 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 4 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 5 + animatorController: {fileID: 9100000, guid: 9aedd84d3acd0db4384c04f9b136ecb1, + type: 2} + mask: {fileID: 0} + isDefault: 0 + specialAnimationLayers: + - isEnabled: 0 + type: 6 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 7 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 8 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + AnimationPreset: {fileID: 0} + animationHashSet: [] + autoFootsteps: 1 + autoLocomotion: 1 + collider_head: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_torso: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} +--- !u!114 &8742850335181783537 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3649483952032229877} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + m_Name: + m_EditorClassIdentifier: + launchedFromSDKPipeline: 0 + completedSDKPipeline: 0 + blueprintId: + contentType: 0 + assetBundleUnityVersion: + fallbackStatus: 0 diff --git a/UnitTests~/Animation/MMD/MMDMode_Overrides.prefab.meta b/UnitTests~/Animation/MMD/MMDMode_Overrides.prefab.meta new file mode 100644 index 00000000..fd1dbc8a --- /dev/null +++ b/UnitTests~/Animation/MMD/MMDMode_Overrides.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1880667773f814c49922924e24005b29 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/Animation/MMD/MMDMode_Reactive.prefab b/UnitTests~/Animation/MMD/MMDMode_Reactive.prefab new file mode 100644 index 00000000..e094e9aa --- /dev/null +++ b/UnitTests~/Animation/MMD/MMDMode_Reactive.prefab @@ -0,0 +1,454 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1386134902351561831 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6342890467153306326} + - component: {fileID: 6952234223613419749} + - component: {fileID: 8569547807942057610} + - component: {fileID: 5205561095749141078} + m_Layer: 0 + m_Name: GameObject Toggle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6342890467153306326 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1386134902351561831} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4605622065467040953} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &6952234223613419749 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1386134902351561831} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a162bb8ec7e24a5abcf457887f1df3fa, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 0 + m_objects: + - Object: + referencePath: GameObject + targetObject: {fileID: 4583133230356327828} + Active: 0 +--- !u!114 &8569547807942057610 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1386134902351561831} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3b29d45007c5493d926d2cd45a489529, type: 3} + m_Name: + m_EditorClassIdentifier: + Control: + name: GameObject Toggle + icon: {fileID: 0} + type: 102 + parameter: + name: + value: 1 + style: 0 + subMenu: {fileID: 0} + subParameters: [] + labels: [] + MenuSource: 1 + menuSource_otherObjectChildren: {fileID: 0} + isSynced: 1 + isSaved: 1 + isDefault: 0 + automaticValue: 1 + label: +--- !u!114 &5205561095749141078 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1386134902351561831} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7ef83cb0c23d4d7c9d41021e544a1978, type: 3} + m_Name: + m_EditorClassIdentifier: + menuToAppend: {fileID: 0} + installTargetMenu: {fileID: 0} +--- !u!1 &4583133230356327828 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3143043948112966587} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3143043948112966587 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4583133230356327828} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4605622065467040953} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &6783015114988609388 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4605622065467040953} + - component: {fileID: 2545551016003718416} + - component: {fileID: 7974059650307163264} + - component: {fileID: 6183713000671264849} + m_Layer: 0 + m_Name: MMDMode_Reactive + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4605622065467040953 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6783015114988609388} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.02920363, y: 0.5853253, z: -0.39815798} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3143043948112966587} + - {fileID: 6342890467153306326} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &2545551016003718416 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6783015114988609388} + m_Enabled: 1 + m_Avatar: {fileID: 0} + m_Controller: {fileID: 0} + m_CullingMode: 0 + m_UpdateMode: 0 + m_ApplyRootMotion: 0 + m_LinearVelocityBlending: 0 + m_StabilizeFeet: 0 + m_WarningMessage: + m_HasTransformHierarchy: 1 + m_AllowConstantClipSamplingOptimization: 1 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 +--- !u!114 &7974059650307163264 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6783015114988609388} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 542108242, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3} + m_Name: + m_EditorClassIdentifier: + Name: + ViewPosition: {x: 0, y: 1.6, z: 0.2} + Animations: 0 + ScaleIPD: 1 + lipSync: 0 + lipSyncJawBone: {fileID: 0} + lipSyncJawClosed: {x: 0, y: 0, z: 0, w: 1} + lipSyncJawOpen: {x: 0, y: 0, z: 0, w: 1} + VisemeSkinnedMesh: {fileID: 0} + MouthOpenBlendShapeName: Facial_Blends.Jaw_Down + VisemeBlendShapes: [] + unityVersion: + portraitCameraPositionOffset: {x: 0, y: 0, z: 0} + portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139} + networkIDs: [] + customExpressions: 1 + expressionsMenu: {fileID: 0} + expressionParameters: {fileID: 0} + enableEyeLook: 0 + customEyeLookSettings: + eyeMovement: + confidence: 0.5 + excitement: 0.5 + leftEye: {fileID: 0} + rightEye: {fileID: 0} + eyesLookingStraight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingUp: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingDown: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingLeft: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyesLookingRight: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidType: 0 + upperLeftEyelid: {fileID: 0} + upperRightEyelid: {fileID: 0} + lowerLeftEyelid: {fileID: 0} + lowerRightEyelid: {fileID: 0} + eyelidsDefault: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsClosed: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingUp: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsLookingDown: + upper: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + lower: + linked: 1 + left: {x: 0, y: 0, z: 0, w: 0} + right: {x: 0, y: 0, z: 0, w: 0} + eyelidsSkinnedMesh: {fileID: 0} + eyelidsBlendshapes: + customizeAnimationLayers: 1 + baseAnimationLayers: + - isEnabled: 0 + type: 0 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 4 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 5 + animatorController: {fileID: 9100000, guid: e7f6321e9d2601a45a3efa0a24305b78, + type: 2} + mask: {fileID: 0} + isDefault: 0 + specialAnimationLayers: + - isEnabled: 0 + type: 6 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 7 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + - isEnabled: 0 + type: 8 + animatorController: {fileID: 0} + mask: {fileID: 0} + isDefault: 1 + AnimationPreset: {fileID: 0} + animationHashSet: [] + autoFootsteps: 1 + autoLocomotion: 1 + collider_head: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_torso: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_footL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_handL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleL: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerIndexR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerMiddleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerRingR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + collider_fingerLittleR: + isMirrored: 1 + state: 0 + transform: {fileID: 0} + radius: 0 + height: 0 + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} +--- !u!114 &6183713000671264849 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6783015114988609388} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3} + m_Name: + m_EditorClassIdentifier: + launchedFromSDKPipeline: 0 + completedSDKPipeline: 0 + blueprintId: + contentType: 0 + assetBundleUnityVersion: + fallbackStatus: 0 diff --git a/UnitTests~/Animation/MMD/MMDMode_Reactive.prefab.meta b/UnitTests~/Animation/MMD/MMDMode_Reactive.prefab.meta new file mode 100644 index 00000000..b727ac81 --- /dev/null +++ b/UnitTests~/Animation/MMD/MMDMode_Reactive.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 38d357295bfc91b499787e9c3e9e5fc1 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/Animation/MMD/Overrides.controller b/UnitTests~/Animation/MMD/Overrides.controller new file mode 100644 index 00000000..e55da41c --- /dev/null +++ b/UnitTests~/Animation/MMD/Overrides.controller @@ -0,0 +1,340 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-7355338869790508137 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!114 &-6326018740725076774 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d1d979d3cedd4ddd969f414e2ea04fb8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_DisableInMMDMode: 1 +--- !u!1102 &-4845154332522161829 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &-3747905681079763705 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ForceOn + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -4845154332522161829} + m_Position: {x: 461.43958, y: -41.875793, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: + - {fileID: -6326018740725076774} + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: -4845154332522161829} +--- !u!1107 &-3477861051435458144 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Base + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -7355338869790508137} + m_Position: {x: 441.0094, y: 127.91522, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: -7355338869790508137} +--- !u!1107 &-3163258767259997666 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: DefaultOn + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 2165750007709086016} + m_Position: {x: 320, y: 130, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 2165750007709086016} +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Overrides + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: Base + m_StateMachine: {fileID: -3477861051435458144} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 0 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: ForceOff + m_StateMachine: {fileID: 3734055781436131242} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 1 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: DefaultOn + m_StateMachine: {fileID: -3163258767259997666} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 1 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: DefaultOff + m_StateMachine: {fileID: 6393666339861909369} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 1 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: ForceOn + m_StateMachine: {fileID: -3747905681079763705} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 1 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} +--- !u!1102 &2165750007709086016 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!114 &3283455143269254731 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d1d979d3cedd4ddd969f414e2ea04fb8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_DisableInMMDMode: 0 +--- !u!1102 &3470771388114793831 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &3734055781436131242 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ForceOff + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 3470771388114793831} + m_Position: {x: 495.48987, y: 46.194702, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: + - {fileID: 3283455143269254731} + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 3470771388114793831} +--- !u!1107 &6393666339861909369 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: DefaultOff + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 8588877275217762460} + m_Position: {x: 638.5006, y: 477.95697, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 8588877275217762460} +--- !u!1102 &8588877275217762460 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 931318d79958463468b9ca3d48c96186, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: diff --git a/UnitTests~/Animation/MMD/Overrides.controller.meta b/UnitTests~/Animation/MMD/Overrides.controller.meta new file mode 100644 index 00000000..0f27c152 --- /dev/null +++ b/UnitTests~/Animation/MMD/Overrides.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9aedd84d3acd0db4384c04f9b136ecb1 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/Animation/MMD/empty.anim b/UnitTests~/Animation/MMD/empty.anim new file mode 100644 index 00000000..584aa3d6 --- /dev/null +++ b/UnitTests~/Animation/MMD/empty.anim @@ -0,0 +1,53 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!74 &7400000 +AnimationClip: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: empty + serializedVersion: 7 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: [] + m_PPtrCurves: [] + m_SampleRate: 60 + m_WrapMode: 0 + m_Bounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 0, y: 0, z: 0} + m_ClipBindingConstant: + genericBindings: [] + pptrCurveMapping: [] + m_AnimationClipSettings: + serializedVersion: 2 + m_AdditiveReferencePoseClip: {fileID: 0} + m_AdditiveReferencePoseTime: 0 + m_StartTime: 0 + m_StopTime: 1 + m_OrientationOffsetY: 0 + m_Level: 0 + m_CycleOffset: 0 + m_HasAdditiveReferencePose: 0 + m_LoopTime: 0 + m_LoopBlend: 0 + m_LoopBlendOrientation: 0 + m_LoopBlendPositionY: 0 + m_LoopBlendPositionXZ: 0 + m_KeepOriginalOrientation: 0 + m_KeepOriginalPositionY: 1 + m_KeepOriginalPositionXZ: 0 + m_HeightFromFeet: 0 + m_Mirror: 0 + m_EditorCurves: [] + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/UnitTests~/Animation/MMD/empty.anim.meta b/UnitTests~/Animation/MMD/empty.anim.meta new file mode 100644 index 00000000..f51fb0e5 --- /dev/null +++ b/UnitTests~/Animation/MMD/empty.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 931318d79958463468b9ca3d48c96186 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/Animation/MergeBlendTreeTest.cs b/UnitTests~/Animation/MergeBlendTreeTest.cs index d1b934cc..5635107f 100644 --- a/UnitTests~/Animation/MergeBlendTreeTest.cs +++ b/UnitTests~/Animation/MergeBlendTreeTest.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core.editor; using NUnit.Framework; @@ -115,7 +116,7 @@ namespace modular_avatar_tests var layerNames = (FindController(root, VRCAvatarDescriptor.AnimLayerType.FX).animatorController as AnimatorController) .layers.Select(l => l.name).ToArray(); - Assert.AreEqual(new[] {MergeBlendTreePass.BlendTreeLayerName, "m2", "Eyes", "FaceMood", "m1", "m3"}, layerNames); + Assert.AreEqual(new[] {MergeBlendTreePass.BlendTreeLayerName, MMDRelayPass.ControlLayerName, MMDRelayPass.DummyLayerName, "m2", "Eyes", "FaceMood", "m1", "m3"}, layerNames); } ModularAvatarMergeAnimator TestMerge(GameObject root, string mergeName, Motion motion = null) diff --git a/UnitTests~/Animation/MergeOrderTest.cs b/UnitTests~/Animation/MergeOrderTest.cs index 31831d44..6c45a542 100644 --- a/UnitTests~/Animation/MergeOrderTest.cs +++ b/UnitTests~/Animation/MergeOrderTest.cs @@ -1,6 +1,7 @@ #if MA_VRCSDK3_AVATARS using System.Linq; +using nadena.dev.modular_avatar.animation; using nadena.dev.ndmf; using NUnit.Framework; using UnityEditor.Animations; @@ -24,7 +25,7 @@ namespace modular_avatar_tests Assert.AreEqual(new [] { - "1", "2", "3", "4", "5" + "1", MMDRelayPass.ControlLayerName, MMDRelayPass.DummyLayerName, "2", "3", "4", "5" }, layerNames); } } diff --git a/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs b/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs index b96368a0..da7862f4 100644 --- a/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs +++ b/UnitTests~/MergeAnimatorTests/Replacement/MergeAnimatorReplacementTest.cs @@ -1,5 +1,6 @@ using System.Linq; using modular_avatar_tests; +using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core.editor; using nadena.dev.ndmf; @@ -20,10 +21,12 @@ namespace UnitTests.MergeAnimatorTests.Replacement var fx = FindFxController(prefab); var fxc = (AnimatorController)fx.animatorController; + + var layers = fxc.layers.Where(l => !MMDRelayPass.IsRelayLayer(l.name)).ToList(); - Assert.AreEqual(2, fxc.layers.Length); - Assert.AreEqual("2", fxc.layers[0].name); - Assert.AreEqual("3", fxc.layers[1].name); + Assert.AreEqual(2, layers.Count); + Assert.AreEqual("2", layers[0].name); + Assert.AreEqual("3", layers[1].name); } [Test] @@ -38,9 +41,11 @@ namespace UnitTests.MergeAnimatorTests.Replacement var fx = FindFxController(prefab); var fxc = (AnimatorController)fx.animatorController; - Assert.AreEqual(2, fxc.layers.Length); - Assert.AreEqual("3", fxc.layers[0].name); - Assert.AreEqual("2", fxc.layers[1].name); + var layers = fxc.layers.Where(l => !MMDRelayPass.IsRelayLayer(l.name)).ToList(); + + Assert.AreEqual(2, layers.Count); + Assert.AreEqual("3", layers[0].name); + Assert.AreEqual("2", layers[1].name); } [Test] diff --git a/UnitTests~/MergeAnimatorTests/WriteDefaults/WriteDefaultsMergeTests.cs b/UnitTests~/MergeAnimatorTests/WriteDefaults/WriteDefaultsMergeTests.cs index 1fbae006..921349b6 100644 --- a/UnitTests~/MergeAnimatorTests/WriteDefaults/WriteDefaultsMergeTests.cs +++ b/UnitTests~/MergeAnimatorTests/WriteDefaults/WriteDefaultsMergeTests.cs @@ -1,4 +1,5 @@ using modular_avatar_tests; +using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.core; using nadena.dev.modular_avatar.core.editor; using nadena.dev.ndmf.animator; @@ -40,6 +41,9 @@ namespace UnitTests.MergeAnimatorTests.WriteDefaults foreach (var layer in vfx.Layers) { bool expectedState; + + if (MMDRelayPass.IsRelayLayer(layer.Name)) continue; + switch (layer.Name[0]) { case 'M': expectedState = wdMode ?? mergeSetMode; break; diff --git a/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs b/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs index 11184ee8..2296232f 100644 --- a/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs +++ b/UnitTests~/SyncedLayerHandling/SyncedLayerHandling.cs @@ -4,6 +4,7 @@ using System.Linq; using nadena.dev.modular_avatar.core.editor; using NUnit.Framework; using UnityEditor.Animations; +using UnityEngine; using VRC.SDK3.Avatars.Components; namespace modular_avatar_tests.SyncedLayerHandling @@ -19,7 +20,7 @@ namespace modular_avatar_tests.SyncedLayerHandling var mainLayer = findFxLayer(prefab, "main"); var syncLayer = findFxLayer(prefab, "sync"); - Assert.AreEqual(1, syncLayer.syncedLayerIndex); + Assert.AreEqual(FindFxLayerIndex(prefab, mainLayer), syncLayer.syncedLayerIndex); var m1State = FindStateInLayer(mainLayer, "m1"); var m2State = FindStateInLayer(mainLayer, "m2"); @@ -42,7 +43,7 @@ namespace modular_avatar_tests.SyncedLayerHandling var mainLayer = findFxLayer(prefab, "main"); var syncLayer = findFxLayer(prefab, "sync"); - Assert.AreEqual(2, syncLayer.syncedLayerIndex); + Assert.AreEqual(FindFxLayerIndex(prefab, mainLayer), syncLayer.syncedLayerIndex); var m1State = FindStateInLayer(mainLayer, "m1"); var m2State = FindStateInLayer(mainLayer, "m2"); @@ -61,11 +62,11 @@ namespace modular_avatar_tests.SyncedLayerHandling { var prefab = CreatePrefab("BaseController.prefab"); AvatarProcessor.ProcessAvatar(prefab); - + var mainLayer = findFxLayer(prefab, "main"); var syncLayer = findFxLayer(prefab, "sync"); - Assert.AreEqual(1, syncLayer.syncedLayerIndex); + Assert.AreEqual(FindFxLayerIndex(prefab, mainLayer), syncLayer.syncedLayerIndex); var m1State = FindStateInLayer(mainLayer, "m1"); var overrides = syncLayer.GetOverrideBehaviours(m1State); @@ -86,7 +87,7 @@ namespace modular_avatar_tests.SyncedLayerHandling var mainLayer = findFxLayer(prefab, "main"); var syncLayer = findFxLayer(prefab, "sync"); - Assert.AreEqual(2, syncLayer.syncedLayerIndex); + Assert.AreEqual(FindFxLayerIndex(prefab, mainLayer), syncLayer.syncedLayerIndex); var m1State = FindStateInLayer(mainLayer, "m1"); var overrides = syncLayer.GetOverrideBehaviours(m1State); @@ -108,7 +109,7 @@ namespace modular_avatar_tests.SyncedLayerHandling var mainLayer = findFxLayer(prefab, "main"); var syncLayer = findFxLayer(prefab, "sync"); - Assert.AreEqual(2, syncLayer.syncedLayerIndex); + Assert.AreEqual(FindFxLayerIndex(prefab, mainLayer), syncLayer.syncedLayerIndex); var m1State = FindStateInLayer(mainLayer, "m1"); var overrides = syncLayer.GetOverrideBehaviours(m1State); diff --git a/UnitTests~/TestBase.cs b/UnitTests~/TestBase.cs index da2506d2..b14f41b6 100644 --- a/UnitTests~/TestBase.cs +++ b/UnitTests~/TestBase.cs @@ -165,5 +165,10 @@ namespace modular_avatar_tests .FirstOrDefault(l => l.type == layerType); } #endif + protected int FindFxLayerIndex(GameObject prefab, AnimatorControllerLayer layer) + { + var fx = (AnimatorController)FindFxController(prefab).animatorController; + return fx.layers.TakeWhile(l => l.stateMachine != layer.stateMachine).Count(); + } } } \ No newline at end of file diff --git a/docs~/docs/general-behavior/index.md b/docs~/docs/general-behavior/index.md new file mode 100644 index 00000000..76a29146 --- /dev/null +++ b/docs~/docs/general-behavior/index.md @@ -0,0 +1,7 @@ +--- +sidebar_position: 6 +--- + +# General Behavior + +This section has general information on the behavior of Modular Avatar. \ No newline at end of file diff --git a/docs~/docs/general-behavior/mmd.md b/docs~/docs/general-behavior/mmd.md new file mode 100644 index 00000000..c7c4a571 --- /dev/null +++ b/docs~/docs/general-behavior/mmd.md @@ -0,0 +1,28 @@ +# MMD World Workarounds + +Some "MMD Worlds" in VRChat have a behavior where they will disable the _second and third_ layers of your FX animator +controller. This is intended to disable the layers controlling your facial expressions, so the MMD world can override +them. + +Modular Avatar will automatically arrange for whichever layers were _originally_ layers 2 and 3 to be disabled in this +circumstance. That is, if a layer is added before them, MA will add some relay layers to drive layers 2 and 3 off and +on appropriately. + +Layers added via Merge Animator (even in replace mode) will not be affected by this MMD world behavior; if necessary, +padding layers will be added to protect them. If you want to opt them into this behavior, you can attach the `MA MMD +Layer Control` _state machine behavior_ to the layer you want to control. + +:::warning + +The `MA MMD Layer Control` state machine behavior will only work when attached to the layer directly. Due to how state +machine behaviors work, I can't stop you from attaching them to individual states - but this will break your build +(so don't do that). + +::: + +:::note + +This workaround only works for worlds which specifically disable layers 2 & 3. Given current VRChat constraints, it's +not possible to provide a more general solution. + +::: \ No newline at end of file diff --git a/docs~/docs/problems/index.md b/docs~/docs/problems/index.md index 48e8e3bb..9596ef47 100644 --- a/docs~/docs/problems/index.md +++ b/docs~/docs/problems/index.md @@ -1,5 +1,5 @@ --- -sidebar_position: 6 +sidebar_position: 7 --- # Dealing with problems diff --git a/docs~/docs/reference/mmd-layer-control.md b/docs~/docs/reference/mmd-layer-control.md new file mode 100644 index 00000000..37e40e4a --- /dev/null +++ b/docs~/docs/reference/mmd-layer-control.md @@ -0,0 +1,3 @@ +# MMD Layer Control + +Refer to the [documentation on MMD handling](../general-behavior/mmd). \ No newline at end of file diff --git a/docs~/docs/unity-2019/parameters-devmode.png b/docs~/docs/unity-2019/parameters-devmode.png index 1bef836684434aad62e72fbdb66ae190320f28de..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 31219 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 Jfu>!|{{o!@M(O|n diff --git a/docs~/docs/unity-2019/parameters-enduser.png b/docs~/docs/unity-2019/parameters-enduser.png index 88aa6a62135ba82e80dadb24e88c254dc28d1c01..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 13338 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 SbMXH>cuEgdQMpJ%|Njq=Tg%n} diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/general-behavior/index.md b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/general-behavior/index.md new file mode 100644 index 00000000..613b3742 --- /dev/null +++ b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/general-behavior/index.md @@ -0,0 +1,7 @@ +--- +sidebar_position: 6 +--- + +# その他の仕様 + +Modular Avatar の一般的な挙動に関する情報です。 \ No newline at end of file diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/general-behavior/mmd.md b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/general-behavior/mmd.md new file mode 100644 index 00000000..1ffe587f --- /dev/null +++ b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/general-behavior/mmd.md @@ -0,0 +1,32 @@ +# MMD ワールド対策 + +一部の「MMD ワールド」では、FXアニメーターコントローラーの2番目と3番目_のレイヤーを無効にする動作があります。 +これは、表情を制御するレイヤーを無効にして、MMD ワールドがそれを上書きできるようにするためです。 + +Modular Avatar は、元々レイヤー2と3であったレイヤーがこの状況で無効になるように自動的に配慮します。 +つまり、レイヤーがそれらより前に追加された場合、MA はレイヤー2と3を適切にオンとオフするためのリレーレイヤーを追加します。 + +Merge Animatorなどで追加されたレイヤーは、(置換モードでも)この MMD ワールドの動作に影響を受けません。 +必要に応じて、それらを保護するためのパディングレイヤーが追加されます。MMDワールドの動作で無効化したい場合は、 +`MA MMD Layer Control` という_ステートマシンビヘイビア_を制御したいレイヤーに追加することができます。 + + +:::warning + +`MA MMD Layer Control` ステートマシンビヘイビアは、レイヤーに直接アタッチされている場合にのみ機能します。 +ステートマシンビヘイビアの仕様により、個々のステートに追加することはできてしまいますが、その場合はビルドが失敗するのでやめましょう。 + +::: + +:::warning + +`MA MMD Layer Control`は現在、FXアニメーターコントローラーのレイヤーにのみ適用されます。 + +::: + +:::note + +このワークアラウンドは、2番と3番レイヤーを無効にするワールドにのみ正しく適用されます。 +現在のVRChatの制約により、より一般的な解決策を提供することはできません。 + +::: \ No newline at end of file diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/mmd-layer-control.md b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/mmd-layer-control.md new file mode 100644 index 00000000..8c69f487 --- /dev/null +++ b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/mmd-layer-control.md @@ -0,0 +1,3 @@ +# MMD Layer Control + +[MMD ワールド対策のドキュメンテーション](../general-behavior/mmd)を参照してください。 \ No newline at end of file