diff --git a/.github/ProjectRoot/vpm-manifest-2022.json b/.github/ProjectRoot/vpm-manifest-2022.json index d4bb97fb..60d7c06f 100644 --- a/.github/ProjectRoot/vpm-manifest-2022.json +++ b/.github/ProjectRoot/vpm-manifest-2022.json @@ -19,7 +19,7 @@ "dependencies": {} }, "nadena.dev.ndmf": { - "version": "1.5.0-rc.4" + "version": "1.5.3" } } } \ No newline at end of file diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 82bb1ed1..ec465e5c 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -122,7 +122,7 @@ jobs: workingDirectory: docs-site~ - name: Purge cache - uses: nathanvaughn/actions-cloudflare-purge@aa1121a867565ea71b60f445f441544df0c7b0b9 + uses: nathanvaughn/actions-cloudflare-purge@992cc4e96422fb8ddf077281678373fe41e7736c continue-on-error: true with: cf_zone: ${{ secrets.CF_ZONE_ID }} diff --git a/.github/workflows/gameci.yml b/.github/workflows/gameci.yml index 2d30547d..adf05e8e 100644 --- a/.github/workflows/gameci.yml +++ b/.github/workflows/gameci.yml @@ -116,6 +116,7 @@ jobs: with: repos: | https://vpm.nadena.dev/vpm-prerelease.json + https://vrchat.github.io/packages/index.json?download - if: ${{ steps.setup.outputs.should_test == 'true' }} name: "Debug: List project contents" diff --git a/Editor/Animation/AnimationDatabase.cs b/Editor/Animation/AnimationDatabase.cs index 6c35e7df..dd594092 100644 --- a/Editor/Animation/AnimationDatabase.cs +++ b/Editor/Animation/AnimationDatabase.cs @@ -9,6 +9,7 @@ using nadena.dev.ndmf; using UnityEditor; using UnityEditor.Animations; using UnityEngine; +using UnityEngine.Profiling; using BuildContext = nadena.dev.ndmf.BuildContext; #if MA_VRCSDK3_AVATARS using VRC.SDK3.Avatars.Components; @@ -52,7 +53,21 @@ namespace nadena.dev.modular_avatar.animation set { _originalClip = value; - IsProxyAnimation = value != null && Util.IsProxyAnimation(value); + + var baseClip = ObjectRegistry.GetReference(value)?.Object as AnimationClip; + + IsProxyAnimation = false; + if (value != null && Util.IsProxyAnimation(value)) + { + IsProxyAnimation = true; + } + else if (baseClip != null && Util.IsProxyAnimation(baseClip)) + { + // RenameParametersPass replaces proxy clips outside of the purview of the animation database, + // so trace this using ObjectRegistry and correct the reference. + IsProxyAnimation = true; + _originalClip = baseClip; + } } } @@ -97,11 +112,13 @@ namespace nadena.dev.modular_avatar.animation internal void Commit() { + Profiler.BeginSample("AnimationDatabase.Commit"); foreach (var clip in _clips) { if (clip.IsProxyAnimation) clip.CurrentClip = clip.OriginalClip; } + Profiler.BeginSample("UpdateClipProperties"); foreach (var clip in _clips) { // Changing the "high quality curve" setting can result in behavior changes (but can happen accidentally @@ -121,11 +138,16 @@ namespace nadena.dev.modular_avatar.animation } } } + Profiler.EndSample(); + Profiler.BeginSample("ClipCommitActions"); foreach (var action in _clipCommitActions) { action(); } + Profiler.EndSample(); + + Profiler.EndSample(); } internal void OnActivate(BuildContext context) @@ -192,7 +214,11 @@ namespace nadena.dev.modular_avatar.animation var clipHolder = RegisterMotion(state.motion, state, processClip, _originalToHolder); state.motion = clipHolder.CurrentClip; - _clipCommitActions.Add(() => { state.motion = clipHolder.CurrentClip; }); + _clipCommitActions.Add(() => + { + state.motion = clipHolder.CurrentClip; + MaybeSaveClip(clipHolder.CurrentClip); + }); } internal void ForeachClip(Action processClip) @@ -368,6 +394,8 @@ namespace nadena.dev.modular_avatar.animation children[i].motion = curClip; dirty = true; } + + MaybeSaveClip(curClip); } if (dirty) @@ -379,5 +407,23 @@ namespace nadena.dev.modular_avatar.animation return treeHolder; } + + private void MaybeSaveClip(Motion curClip) + { + Profiler.BeginSample("MaybeSaveClip"); + if (curClip != null && !EditorUtility.IsPersistent(curClip) && EditorUtility.IsPersistent(_context.AssetContainer) && _context.AssetContainer != null) + { + try + { + AssetDatabase.AddObjectToAsset(curClip, _context.AssetContainer); + } + catch (Exception e) + { + Debug.LogException(e); + throw; + } + } + Profiler.EndSample(); + } } } \ No newline at end of file diff --git a/Editor/Animation/AnimationUtil.cs b/Editor/Animation/AnimationUtil.cs index 61e09659..aa026548 100644 --- a/Editor/Animation/AnimationUtil.cs +++ b/Editor/Animation/AnimationUtil.cs @@ -42,7 +42,9 @@ namespace nadena.dev.modular_avatar.animation throw new Exception("Unknown RuntimeAnimatorContoller type " + controller.GetType()); } - return merger.Finish(); + var clone = merger.Finish(); + ObjectRegistry.RegisterReplacedObject(controller, clone); + return clone; } internal static void CloneAllControllers(BuildContext context) diff --git a/Editor/Animation/AnimatorCombiner.cs b/Editor/Animation/AnimatorCombiner.cs index 36ddf4de..6a64fe28 100644 --- a/Editor/Animation/AnimatorCombiner.cs +++ b/Editor/Animation/AnimatorCombiner.cs @@ -573,6 +573,8 @@ namespace nadena.dev.modular_avatar.animation private AnimatorStateMachine mapStateMachine(string basePath, AnimatorStateMachine layerStateMachine) { + if (layerStateMachine == null) return null; + var cacheKey = new KeyValuePair(basePath, layerStateMachine); if (_stateMachines.TryGetValue(cacheKey, out var asm)) @@ -611,7 +613,7 @@ namespace nadena.dev.modular_avatar.animation { if (!string.IsNullOrEmpty(playAudio.SourcePath) && !string.IsNullOrEmpty(basePath) && !playAudio.SourcePath.StartsWith(basePath)) { - playAudio.SourcePath = $"{basePath}/{playAudio.SourcePath}"; + playAudio.SourcePath = $"{basePath}{playAudio.SourcePath}"; } break; } diff --git a/Editor/Animation/DeepClone.cs b/Editor/Animation/DeepClone.cs index 4667733a..e84bac6f 100644 --- a/Editor/Animation/DeepClone.cs +++ b/Editor/Animation/DeepClone.cs @@ -93,6 +93,11 @@ namespace nadena.dev.modular_avatar.animation { ObjectRegistry.RegisterReplacedObject(original, obj); } + + if (_isSaved && !EditorUtility.IsPersistent(obj)) + { + AssetDatabase.AddObjectToAsset(obj, _combined); + } return (T)obj; } diff --git a/Editor/Animation/PathMappings.cs b/Editor/Animation/PathMappings.cs index bbd6648b..9546a063 100644 --- a/Editor/Animation/PathMappings.cs +++ b/Editor/Animation/PathMappings.cs @@ -8,6 +8,7 @@ using nadena.dev.ndmf.util; using UnityEditor; using UnityEditor.Animations; using UnityEngine; +using UnityEngine.Profiling; #if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER #endif @@ -337,8 +338,10 @@ namespace nadena.dev.modular_avatar.animation internal void OnDeactivate(BuildContext context) { + Profiler.BeginSample("PathMappings.OnDeactivate"); Dictionary clipCache = new Dictionary(); - + + Profiler.BeginSample("ApplyMappingsToClip"); _animationDatabase.ForeachClip(holder => { if (holder.CurrentClip is AnimationClip clip) @@ -346,23 +349,29 @@ namespace nadena.dev.modular_avatar.animation holder.CurrentClip = ApplyMappingsToClip(clip, clipCache); } }); + Profiler.EndSample(); #if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER + Profiler.BeginSample("MapPlayAudio"); _animationDatabase.ForeachPlayAudio(playAudio => { if (playAudio == null) return; playAudio.SourcePath = MapPath(playAudio.SourcePath, true); }); + Profiler.EndSample(); #endif + Profiler.BeginSample("InvokeIOnCommitObjectRenamesCallbacks"); foreach (var listener in context.AvatarRootObject.GetComponentsInChildren()) { listener.OnCommitObjectRenames(context, this); } + Profiler.EndSample(); var layers = context.AvatarDescriptor.baseAnimationLayers .Concat(context.AvatarDescriptor.specialAnimationLayers); + Profiler.BeginSample("ApplyMappingsToAvatarMasks"); foreach (var layer in layers) { ApplyMappingsToAvatarMask(layer.mask); @@ -373,6 +382,9 @@ namespace nadena.dev.modular_avatar.animation foreach (var acLayer in ac.layers) ApplyMappingsToAvatarMask(acLayer.avatarMask); } + Profiler.EndSample(); + + Profiler.EndSample(); } public GameObject PathToObject(string path) diff --git a/Editor/ApplyAnimatorDefaultValuesPass.cs b/Editor/ApplyAnimatorDefaultValuesPass.cs index f2554d13..7b3c91ed 100644 --- a/Editor/ApplyAnimatorDefaultValuesPass.cs +++ b/Editor/ApplyAnimatorDefaultValuesPass.cs @@ -40,7 +40,7 @@ namespace nadena.dev.modular_avatar.core.editor switch (parameters[i].type) { case AnimatorControllerParameterType.Bool: - parameters[i].defaultBool = defaultValue > 0.5f; + parameters[i].defaultBool = defaultValue != 0.0f; break; case AnimatorControllerParameterType.Int: parameters[i].defaultInt = Mathf.RoundToInt(defaultValue); diff --git a/Editor/HarmonyPatches/PatchLoader.cs b/Editor/HarmonyPatches/PatchLoader.cs index 0ce7ffa4..b368fca7 100644 --- a/Editor/HarmonyPatches/PatchLoader.cs +++ b/Editor/HarmonyPatches/PatchLoader.cs @@ -11,6 +11,8 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches { internal class PatchLoader { + private const string HarmonyId = "nadena.dev.modular_avatar"; + private static readonly Action[] patches = new Action[] { //HierarchyViewPatches.Patch, @@ -19,7 +21,7 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches [InitializeOnLoadMethod] static void ApplyPatches() { - var harmony = new Harmony("nadena.dev.modular_avatar"); + var harmony = new Harmony(HarmonyId); foreach (var patch in patches) { @@ -33,7 +35,7 @@ namespace nadena.dev.modular_avatar.core.editor.HarmonyPatches } } - AssemblyReloadEvents.beforeAssemblyReload += () => { harmony.UnpatchAll(); }; + AssemblyReloadEvents.beforeAssemblyReload += () => { harmony.UnpatchAll(HarmonyId); }; } } } \ No newline at end of file diff --git a/Editor/HeuristicBoneMapper.cs b/Editor/HeuristicBoneMapper.cs index 3b11051f..9c4b03ee 100644 --- a/Editor/HeuristicBoneMapper.cs +++ b/Editor/HeuristicBoneMapper.cs @@ -46,7 +46,7 @@ namespace nadena.dev.modular_avatar.core.editor new[] {"LeftFoot", "Foot_Left", "Foot_L", "Ankle_L", "Foot.L.001", "Left ankle", "heel.L", "heel"}, new[] {"RightFoot", "Foot_Right", "Foot_R", "Ankle_R", "Foot.R.001", "Right ankle", "heel.R", "heel"}, new[] {"Spine", "spine01"}, - new[] {"Chest", "Bust", "spine02"}, + new[] {"Chest", "Bust", "spine02", "upper_chest"}, new[] {"Neck"}, new[] {"Head"}, new[] {"LeftShoulder", "Shoulder_Left", "Shoulder_L"}, diff --git a/Editor/Inspector/BoneProxyEditor.cs b/Editor/Inspector/BoneProxyEditor.cs index f9d3ec6d..22566984 100644 --- a/Editor/Inspector/BoneProxyEditor.cs +++ b/Editor/Inspector/BoneProxyEditor.cs @@ -95,7 +95,7 @@ namespace nadena.dev.modular_avatar.core.editor var t = (ModularAvatarBoneProxy) targets[i]; Undo.RecordObjects(targets, "Set targets"); var xform = ((TempObjRef) objRefs[i]).target; - if (RuntimeUtil.FindAvatarTransformInParents(xform)?.gameObject != parentAvatar) continue; + if (xform != null && RuntimeUtil.FindAvatarTransformInParents(xform)?.gameObject != parentAvatar) continue; t.target = xform; } } @@ -159,4 +159,4 @@ namespace nadena.dev.modular_avatar.core.editor } } } -} \ No newline at end of file +} diff --git a/Editor/Inspector/DragAndDropManipulator.cs b/Editor/Inspector/DragAndDropManipulator.cs new file mode 100644 index 00000000..0d3de862 --- /dev/null +++ b/Editor/Inspector/DragAndDropManipulator.cs @@ -0,0 +1,106 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace nadena.dev.modular_avatar.core.editor +{ + internal abstract class DragAndDropManipulator : PointerManipulator where T : Component, IHaveObjReferences + { + private const string DragActiveClassName = "drop-area--drag-active"; + + public T TargetComponent { get; set; } + + protected virtual bool AllowKnownObjects => true; + + private Transform _avatarRoot; + private GameObject[] _draggingObjects = Array.Empty(); + + public DragAndDropManipulator(VisualElement targetElement, T targetComponent) + { + target = targetElement; + TargetComponent = targetComponent; + } + + protected sealed override void RegisterCallbacksOnTarget() + { + target.RegisterCallback(OnDragEnter); + target.RegisterCallback(OnDragLeave); + target.RegisterCallback(OnDragExited); + target.RegisterCallback(OnDragUpdated); + target.RegisterCallback(OnDragPerform); + } + + protected sealed override void UnregisterCallbacksFromTarget() + { + target.UnregisterCallback(OnDragEnter); + target.UnregisterCallback(OnDragLeave); + target.UnregisterCallback(OnDragExited); + target.UnregisterCallback(OnDragUpdated); + target.UnregisterCallback(OnDragPerform); + } + + private void OnDragEnter(DragEnterEvent _) + { + if (TargetComponent == null) return; + + _avatarRoot = RuntimeUtil.FindAvatarTransformInParents(TargetComponent.transform); + if (_avatarRoot == null) return; + + var knownObjects = TargetComponent.GetObjectReferences().Select(x => x.Get(TargetComponent)).ToHashSet(); + _draggingObjects = DragAndDrop.objectReferences.OfType() + .Where(x => AllowKnownObjects || !knownObjects.Contains(x)) + .Where(x => RuntimeUtil.FindAvatarTransformInParents(x.transform) == _avatarRoot) + .Where(FilterGameObject) + .ToArray(); + if (_draggingObjects.Length == 0) return; + + target.AddToClassList(DragActiveClassName); + } + + private void OnDragLeave(DragLeaveEvent _) + { + _draggingObjects = Array.Empty(); + target.RemoveFromClassList(DragActiveClassName); + } + + private void OnDragExited(DragExitedEvent _) + { + _draggingObjects = Array.Empty(); + target.RemoveFromClassList(DragActiveClassName); + } + + private void OnDragUpdated(DragUpdatedEvent _) + { + if (TargetComponent == null) return; + if (_avatarRoot == null) return; + if (_draggingObjects.Length == 0) return; + + DragAndDrop.visualMode = DragAndDropVisualMode.Generic; + } + + private void OnDragPerform(DragPerformEvent _) + { + if (TargetComponent == null) return; + if (_avatarRoot == null) return; + if (_draggingObjects.Length == 0) return; + + AddObjectReferences(_draggingObjects + .Select(x => + { + var reference = new AvatarObjectReference(); + reference.Set(x); + return reference; + }) + .ToArray()); + } + + protected virtual bool FilterGameObject(GameObject obj) + { + return true; + } + + protected abstract void AddObjectReferences(AvatarObjectReference[] references); + } +} diff --git a/Editor/Inspector/DragAndDropManipulator.cs.meta b/Editor/Inspector/DragAndDropManipulator.cs.meta new file mode 100644 index 00000000..b78bdf93 --- /dev/null +++ b/Editor/Inspector/DragAndDropManipulator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 528c660b56905844ea2f88bc73837e9f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Inspector/FirstPersonVisibleEditor.cs b/Editor/Inspector/FirstPersonVisibleEditor.cs index 81b51ad2..b9646ddb 100644 --- a/Editor/Inspector/FirstPersonVisibleEditor.cs +++ b/Editor/Inspector/FirstPersonVisibleEditor.cs @@ -19,12 +19,6 @@ namespace nadena.dev.modular_avatar.core.editor { var target = (ModularAvatarVisibleHeadAccessory) this.target; - -#if UNITY_ANDROID - EditorGUILayout.HelpBox(Localization.S("fpvisible.quest"), MessageType.Warning); - -#else - if (_validation != null) { var status = _validation.Validate(target); @@ -35,6 +29,9 @@ namespace nadena.dev.modular_avatar.core.editor case VisibleHeadAccessoryValidation.ReadyStatus.ParentMarked: EditorGUILayout.HelpBox(Localization.S("fpvisible.normal"), MessageType.Info); break; + case VisibleHeadAccessoryValidation.ReadyStatus.NotUnderHead: + EditorGUILayout.HelpBox(Localization.S("fpvisible.NotUnderHead"), MessageType.Warning); + break; default: { var label = "fpvisible." + status; @@ -44,9 +41,7 @@ namespace nadena.dev.modular_avatar.core.editor } } -#endif - Localization.ShowLanguageUI(); } } -} \ No newline at end of file +} diff --git a/Editor/Inspector/LogoDisplay.cs b/Editor/Inspector/LogoDisplay.cs index c583b297..43b55051 100644 --- a/Editor/Inspector/LogoDisplay.cs +++ b/Editor/Inspector/LogoDisplay.cs @@ -18,7 +18,7 @@ namespace nadena.dev.modular_avatar.core.editor { return (EditorStyles.label?.lineHeight ?? 0) * 3; } - catch (NullReferenceException e) + catch (NullReferenceException) { // This can happen in early initialization... return 0; diff --git a/Editor/Inspector/MaterialSetter/MaterialSetterEditor.cs b/Editor/Inspector/MaterialSetter/MaterialSetterEditor.cs index d13532c4..a2b055b3 100644 --- a/Editor/Inspector/MaterialSetter/MaterialSetterEditor.cs +++ b/Editor/Inspector/MaterialSetter/MaterialSetterEditor.cs @@ -16,10 +16,11 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger [SerializeField] private StyleSheet uss; [SerializeField] private VisualTreeAsset uxml; + private DragAndDropManipulator _dragAndDropManipulator; protected override void OnInnerInspectorGUI() { - throw new NotImplementedException(); + EditorGUILayout.HelpBox("Unable to show override changes", MessageType.Info); } protected override VisualElement CreateInnerInspectorGUI() @@ -37,7 +38,44 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger listView.showBoundCollectionSize = false; listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight; + _dragAndDropManipulator = new DragAndDropManipulator(root.Q("group-box"), target as ModularAvatarMaterialSetter); + return root; } + + private void OnEnable() + { + if (_dragAndDropManipulator != null) + _dragAndDropManipulator.TargetComponent = target as ModularAvatarMaterialSetter; + } + + private class DragAndDropManipulator : DragAndDropManipulator + { + public DragAndDropManipulator(VisualElement targetElement, ModularAvatarMaterialSetter targetComponent) + : base(targetElement, targetComponent) { } + + protected override bool FilterGameObject(GameObject obj) + { + if (obj.TryGetComponent(out var renderer)) + { + return renderer.sharedMaterials.Length > 0; + } + return false; + } + + protected override void AddObjectReferences(AvatarObjectReference[] references) + { + Undo.RecordObject(TargetComponent, "Add Material Switch Objects"); + + foreach (var reference in references) + { + var materialSwitchObject = new MaterialSwitchObject { Object = reference, MaterialIndex = 0 }; + TargetComponent.Objects.Add(materialSwitchObject); + } + + EditorUtility.SetDirty(TargetComponent); + PrefabUtility.RecordPrefabInstancePropertyModifications(TargetComponent); + } + } } } \ No newline at end of file diff --git a/Editor/Inspector/MaterialSetter/MaterialSetterStyles.uss b/Editor/Inspector/MaterialSetter/MaterialSetterStyles.uss index 84204231..8e422d2d 100644 --- a/Editor/Inspector/MaterialSetter/MaterialSetterStyles.uss +++ b/Editor/Inspector/MaterialSetter/MaterialSetterStyles.uss @@ -62,3 +62,13 @@ #f-material { flex-grow: 1; } + +.drop-area--drag-active { + background-color: rgba(0, 127, 255, 0.2); +} + +.drop-area--drag-active .unity-scroll-view, +.drop-area--drag-active .unity-list-view__footer, +.drop-area--drag-active .unity-list-view__reorderable-item { + background-color: rgba(0, 0, 0, 0.0); +} diff --git a/Editor/Inspector/MaterialSetter/MaterialSwitchObjectEditor.cs b/Editor/Inspector/MaterialSetter/MaterialSwitchObjectEditor.cs index f789cc29..95013d55 100644 --- a/Editor/Inspector/MaterialSetter/MaterialSwitchObjectEditor.cs +++ b/Editor/Inspector/MaterialSetter/MaterialSwitchObjectEditor.cs @@ -3,7 +3,6 @@ using UnityEditor; using UnityEditor.UIElements; using UnityEngine; -using UnityEngine.UI; using UnityEngine.UIElements; #endregion @@ -148,7 +147,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger { return targetObject?.GetComponent()?.sharedMaterials; } - catch (MissingComponentException e) + catch (MissingComponentException) { return null; } diff --git a/Editor/Inspector/Menu/MenuItemGUI.cs b/Editor/Inspector/Menu/MenuItemGUI.cs index 67458e40..30439a32 100644 --- a/Editor/Inspector/Menu/MenuItemGUI.cs +++ b/Editor/Inspector/Menu/MenuItemGUI.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Runtime.Serialization; using nadena.dev.modular_avatar.core.menu; using nadena.dev.ndmf; +using nadena.dev.ndmf.preview; using UnityEditor; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -21,6 +23,40 @@ namespace nadena.dev.modular_avatar.core.editor protected override string localizationPrefix => "submenu_source"; } + internal static class ParameterIntrospectionCache + { + internal static PropCache> ProvidedParameterCache = + new("GetParametersForObject", GetParametersForObject_miss); + + internal static PropCache> + ParameterRemappingCache = new("GetParameterRemappingsAt", GetParameterRemappingsAt_miss); + + private static ImmutableList GetParametersForObject_miss(ComputeContext ctx, GameObject obj) + { + if (obj == null) return ImmutableList.Empty; + + return ParameterInfo.ForPreview(ctx).GetParametersForObject(obj).ToImmutableList(); + } + + private static ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> + GetParameterRemappingsAt_miss(ComputeContext ctx, GameObject obj) + { + if (obj == null) return ImmutableDictionary<(ParameterNamespace, string), ParameterMapping>.Empty; + + return ParameterInfo.ForPreview(ctx).GetParameterRemappingsAt(obj); + } + + internal static ImmutableList GetParametersForObject(GameObject avatar) + { + return ProvidedParameterCache.Get(ComputeContext.NullContext, avatar); + } + + internal static ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> GetParameterRemappingsAt(GameObject avatar) + { + return ParameterRemappingCache.Get(ComputeContext.NullContext, avatar); + } + } + internal class MenuItemCoreGUI { private static readonly ObjectIDGenerator IdGenerator = new ObjectIDGenerator(); @@ -134,12 +170,12 @@ namespace nadena.dev.modular_avatar.core.editor Dictionary rootParameters = new(); - foreach (var param in ParameterInfo.ForUI.GetParametersForObject(parentAvatar.gameObject) + foreach (var param in ParameterIntrospectionCache.GetParametersForObject(parentAvatar.gameObject) .Where(p => p.Namespace == ParameterNamespace.Animator) ) rootParameters[param.EffectiveName] = param; - var remaps = ParameterInfo.ForUI.GetParameterRemappingsAt(paramRef); + var remaps = ParameterIntrospectionCache.GetParameterRemappingsAt(paramRef); foreach (var remap in remaps) { if (remap.Key.Item1 != ParameterNamespace.Animator) continue; @@ -610,14 +646,16 @@ namespace nadena.dev.modular_avatar.core.editor var myMenuItem = serializedObject.targetObject as ModularAvatarMenuItem; if (myMenuItem == null) return null; + var avatarRoot = RuntimeUtil.FindAvatarInParents(myMenuItem.gameObject.transform); + if (avatarRoot == null) return null; + var myParameterName = myMenuItem.Control.parameter.name; if (string.IsNullOrEmpty(myParameterName)) return new List(); - var myMappings = ParameterInfo.ForUI.GetParameterRemappingsAt(myMenuItem.gameObject); + var myMappings = ParameterIntrospectionCache.GetParameterRemappingsAt(myMenuItem.gameObject); if (myMappings.TryGetValue((ParameterNamespace.Animator, myParameterName), out var myReplacement)) myParameterName = myReplacement.ParameterName; - var avatarRoot = RuntimeUtil.FindAvatarInParents(myMenuItem.gameObject.transform); var siblings = new List(); foreach (var otherMenuItem in avatarRoot.GetComponentsInChildren(true)) @@ -627,7 +665,7 @@ namespace nadena.dev.modular_avatar.core.editor var otherParameterName = otherMenuItem.Control.parameter.name; if (string.IsNullOrEmpty(otherParameterName)) continue; - var otherMappings = ParameterInfo.ForUI.GetParameterRemappingsAt(otherMenuItem.gameObject); + var otherMappings = ParameterIntrospectionCache.GetParameterRemappingsAt(otherMenuItem.gameObject); if (otherMappings.TryGetValue((ParameterNamespace.Animator, otherParameterName), out var otherReplacement)) otherParameterName = otherReplacement.ParameterName; diff --git a/Editor/Inspector/Menu/ToggleCreatorShortcut.cs b/Editor/Inspector/Menu/ToggleCreatorShortcut.cs index dfd18712..e549d319 100644 --- a/Editor/Inspector/Menu/ToggleCreatorShortcut.cs +++ b/Editor/Inspector/Menu/ToggleCreatorShortcut.cs @@ -30,7 +30,7 @@ namespace nadena.dev.modular_avatar.core.editor createInstaller = false; } } - catch (MissingComponentException e) + catch (MissingComponentException) { // ignore } diff --git a/Editor/Inspector/MergeArmatureEditor.cs b/Editor/Inspector/MergeArmatureEditor.cs index a2ac88bb..0dbf58f0 100644 --- a/Editor/Inspector/MergeArmatureEditor.cs +++ b/Editor/Inspector/MergeArmatureEditor.cs @@ -84,6 +84,7 @@ namespace nadena.dev.modular_avatar.core.editor } private bool posResetOptionFoldout = false; + private bool posReset_convertATPose = true; private bool posReset_adjustRotation = false; private bool posReset_adjustScale = false; private bool posReset_heuristicRootScale = true; @@ -154,14 +155,17 @@ namespace nadena.dev.modular_avatar.core.editor MessageType.Info ); + posReset_heuristicRootScale = EditorGUILayout.ToggleLeft( + G("merge_armature.reset_pos.heuristic_scale"), + posReset_heuristicRootScale); + posReset_convertATPose = EditorGUILayout.ToggleLeft( + G("merge_armature.reset_pos.convert_atpose"), + posReset_convertATPose); posReset_adjustRotation = EditorGUILayout.ToggleLeft( G("merge_armature.reset_pos.adjust_rotation"), posReset_adjustRotation); posReset_adjustScale = EditorGUILayout.ToggleLeft(G("merge_armature.reset_pos.adjust_scale"), posReset_adjustScale); - posReset_heuristicRootScale = EditorGUILayout.ToggleLeft( - G("merge_armature.reset_pos.heuristic_scale"), - posReset_heuristicRootScale); if (GUILayout.Button(G("merge_armature.reset_pos.execute"))) { @@ -208,6 +212,11 @@ namespace nadena.dev.modular_avatar.core.editor } } + if (posReset_convertATPose) + { + SetupOutfit.FixAPose(RuntimeUtil.FindAvatarTransformInParents(mergeTarget.transform).gameObject, mama.transform, false); + } + if (posReset_heuristicRootScale && !suppressRootScale) { AdjustRootScale(); diff --git a/Editor/Inspector/MoveIndependently/MoveIndependentlyEditor.cs b/Editor/Inspector/MoveIndependently/MoveIndependentlyEditor.cs index 8c6da1a7..4b741d90 100644 --- a/Editor/Inspector/MoveIndependently/MoveIndependentlyEditor.cs +++ b/Editor/Inspector/MoveIndependently/MoveIndependentlyEditor.cs @@ -23,7 +23,7 @@ namespace nadena.dev.modular_avatar.core.editor protected override void OnInnerInspectorGUI() { - throw new NotImplementedException(); + EditorGUILayout.HelpBox("Unable to show override changes", MessageType.Info); } protected override VisualElement CreateInnerInspectorGUI() @@ -61,7 +61,7 @@ namespace nadena.dev.modular_avatar.core.editor var grouped = ctx.Observe(target, t => (t.GroupedBones ?? Array.Empty()) .Select(obj => obj.transform) - .ToHashSet(new ObjectIdentityComparer()), + .ToHashSet(), (x, y) => x.SetEquals(y) ); diff --git a/Editor/Inspector/ObjectToggle/ObjectSwitcherEditor.cs b/Editor/Inspector/ObjectToggle/ObjectSwitcherEditor.cs index 8807e221..999f596c 100644 --- a/Editor/Inspector/ObjectToggle/ObjectSwitcherEditor.cs +++ b/Editor/Inspector/ObjectToggle/ObjectSwitcherEditor.cs @@ -21,7 +21,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger protected override void OnInnerInspectorGUI() { - throw new NotImplementedException(); + EditorGUILayout.HelpBox("Unable to show override changes", MessageType.Info); } protected override VisualElement CreateInnerInspectorGUI() @@ -35,14 +35,12 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger ROSimulatorButton.BindRefObject(root, target); var listView = root.Q("Shapes"); - _dragAndDropManipulator = new DragAndDropManipulator(listView) - { - TargetComponent = target as ModularAvatarObjectToggle - }; listView.showBoundCollectionSize = false; listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight; + _dragAndDropManipulator = new DragAndDropManipulator(root.Q("group-box"), target as ModularAvatarObjectToggle); + return root; } @@ -52,91 +50,25 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger _dragAndDropManipulator.TargetComponent = target as ModularAvatarObjectToggle; } - private class DragAndDropManipulator : PointerManipulator + private class DragAndDropManipulator : DragAndDropManipulator { - public ModularAvatarObjectToggle TargetComponent; - private GameObject[] _nowDragging = Array.Empty(); - private Transform _avatarRoot; + public DragAndDropManipulator(VisualElement targetElement, ModularAvatarObjectToggle targetComponent) + : base(targetElement, targetComponent) { } - private readonly VisualElement _parentElem; + protected override bool AllowKnownObjects => false; - public DragAndDropManipulator(VisualElement target) + protected override void AddObjectReferences(AvatarObjectReference[] references) { - this.target = target; - _parentElem = target.parent; - } + Undo.RecordObject(TargetComponent, "Add Toggled Objects"); - protected override void RegisterCallbacksOnTarget() - { - target.RegisterCallback(OnDragEnter); - target.RegisterCallback(OnDragLeave); - target.RegisterCallback(OnDragPerform); - target.RegisterCallback(OnDragUpdate); - } - - protected override void UnregisterCallbacksFromTarget() - { - target.UnregisterCallback(OnDragEnter); - target.UnregisterCallback(OnDragLeave); - target.UnregisterCallback(OnDragPerform); - target.RegisterCallback(OnDragUpdate); - } - - - private void OnDragEnter(DragEnterEvent evt) - { - if (TargetComponent == null) return; - - _avatarRoot = RuntimeUtil.FindAvatarTransformInParents(TargetComponent.transform); - if (_avatarRoot == null) return; - - _nowDragging = DragAndDrop.objectReferences.OfType() - .Where(o => RuntimeUtil.FindAvatarTransformInParents(o.transform) == _avatarRoot) - .ToArray(); - - if (_nowDragging.Length > 0) + foreach (var reference in references) { - DragAndDrop.visualMode = DragAndDropVisualMode.Link; - - _parentElem.AddToClassList("drop-area--drag-active"); - } - } - - private void OnDragUpdate(DragUpdatedEvent _) - { - if (_nowDragging.Length > 0) DragAndDrop.visualMode = DragAndDropVisualMode.Link; - } - - private void OnDragLeave(DragLeaveEvent evt) - { - _nowDragging = Array.Empty(); - _parentElem.RemoveFromClassList("drop-area--drag-active"); - } - - private void OnDragPerform(DragPerformEvent evt) - { - if (_nowDragging.Length > 0 && TargetComponent != null && _avatarRoot != null) - { - var knownObjs = TargetComponent.Objects.Select(o => o.Object.Get(TargetComponent)).ToHashSet(); - - Undo.RecordObject(TargetComponent, "Add Toggled Objects"); - foreach (var obj in _nowDragging) - { - if (knownObjs.Contains(obj)) continue; - - var aor = new AvatarObjectReference(); - aor.Set(obj); - - var toggledObject = new ToggledObject { Object = aor, Active = !obj.activeSelf }; - TargetComponent.Objects.Add(toggledObject); - } - - EditorUtility.SetDirty(TargetComponent); - PrefabUtility.RecordPrefabInstancePropertyModifications(TargetComponent); + var toggledObject = new ToggledObject { Object = reference, Active = !reference.Get(TargetComponent).activeSelf }; + TargetComponent.Objects.Add(toggledObject); } - _nowDragging = Array.Empty(); - _parentElem.RemoveFromClassList("drop-area--drag-active"); + EditorUtility.SetDirty(TargetComponent); + PrefabUtility.RecordPrefabInstancePropertyModifications(TargetComponent); } } } diff --git a/Editor/Inspector/ObjectToggle/ObjectSwitcherStyles.uss b/Editor/Inspector/ObjectToggle/ObjectSwitcherStyles.uss index e304365a..12402b5d 100644 --- a/Editor/Inspector/ObjectToggle/ObjectSwitcherStyles.uss +++ b/Editor/Inspector/ObjectToggle/ObjectSwitcherStyles.uss @@ -39,14 +39,24 @@ margin: 0; } -#f-active { - justify-content: center; -} - #f-object { flex-grow: 1; } -.drop-area--drag-active > ListView ScrollView { - background-color: rgba(0, 255, 255, 0.1); +#f-active { + display: none; +} + +#f-active-dropdown { + width: 60px; +} + +.drop-area--drag-active { + background-color: rgba(0, 127, 255, 0.2); +} + +.drop-area--drag-active .unity-scroll-view, +.drop-area--drag-active .unity-list-view__footer, +.drop-area--drag-active .unity-list-view__reorderable-item { + background-color: rgba(0, 0, 0, 0.0); } diff --git a/Editor/Inspector/ObjectToggle/ToggledObjectEditor.cs b/Editor/Inspector/ObjectToggle/ToggledObjectEditor.cs index cdb3e622..3bd1719e 100644 --- a/Editor/Inspector/ObjectToggle/ToggledObjectEditor.cs +++ b/Editor/Inspector/ObjectToggle/ToggledObjectEditor.cs @@ -15,6 +15,9 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger private const string UxmlPath = Root + "ToggledObjectEditor.uxml"; private const string UssPath = Root + "ObjectSwitcherStyles.uss"; + private const string V_On = "ON"; + private const string V_Off = "OFF"; + public override VisualElement CreatePropertyGUI(SerializedProperty property) { var uxml = AssetDatabase.LoadAssetAtPath(UxmlPath).CloneTree(); @@ -24,6 +27,21 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger uxml.styleSheets.Add(uss); uxml.BindProperty(property); + var f_active = uxml.Q("f-active"); + var f_active_dropdown = uxml.Q("f-active-dropdown"); + + f_active_dropdown.choices.Add(V_On); + f_active_dropdown.choices.Add(V_Off); + + f_active.RegisterValueChangedCallback(evt => + { + f_active_dropdown.SetValueWithoutNotify(evt.newValue ? V_On : V_Off); + }); + f_active_dropdown.RegisterValueChangedCallback(evt => + { + f_active.value = evt.newValue == V_On; + }); + return uxml; } } diff --git a/Editor/Inspector/ObjectToggle/ToggledObjectEditor.uxml b/Editor/Inspector/ObjectToggle/ToggledObjectEditor.uxml index 0705c9b5..d3498e82 100644 --- a/Editor/Inspector/ObjectToggle/ToggledObjectEditor.uxml +++ b/Editor/Inspector/ObjectToggle/ToggledObjectEditor.uxml @@ -1,6 +1,7 @@  - + + diff --git a/Editor/Inspector/Parameters/DefaultValueField.cs b/Editor/Inspector/Parameters/DefaultValueField.cs index d044a9b1..6ba6cd22 100644 --- a/Editor/Inspector/Parameters/DefaultValueField.cs +++ b/Editor/Inspector/Parameters/DefaultValueField.cs @@ -1,6 +1,4 @@ using System.Globalization; -using UnityEditor; -using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; @@ -15,95 +13,104 @@ namespace nadena.dev.modular_avatar.core.editor private const string V_None = " "; private const string V_True = "ON"; private const string V_False = "OFF"; - - private readonly TextField _visibleField; + private readonly FloatField _defaultValueField; + private readonly Toggle _hasExplicitDefaultValueField; + private readonly TextField _numberField; private readonly DropdownField _boolField; - private readonly Toggle _hasExplicitDefaultSetField; + + private ParameterSyncType _syncType; public DefaultValueField() { // Hidden binding elements _defaultValueField = new FloatField(); - _hasExplicitDefaultSetField = new Toggle(); - _boolField = new DropdownField(); + _defaultValueField.style.display = DisplayStyle.None; + _defaultValueField.bindingPath = nameof(ParameterConfig.defaultValue); + _defaultValueField.RegisterValueChangedCallback(evt => UpdateVisibleField(evt.newValue, _hasExplicitDefaultValueField.value)); + _hasExplicitDefaultValueField = new Toggle(); + _hasExplicitDefaultValueField.style.display = DisplayStyle.None; + _hasExplicitDefaultValueField.bindingPath = nameof(ParameterConfig.hasExplicitDefaultValue); + _hasExplicitDefaultValueField.RegisterValueChangedCallback(evt => UpdateVisibleField(_defaultValueField.value, evt.newValue)); + // Visible elements for input + _numberField = new TextField(); + _numberField.isDelayed = true; + _numberField.RegisterValueChangedCallback(evt => OnUpdateNumberValue(evt.newValue)); + _boolField = new DropdownField(); _boolField.choices.Add(V_None); _boolField.choices.Add(V_True); _boolField.choices.Add(V_False); + _boolField.RegisterValueChangedCallback(evt => OnUpdateBoolValue(evt.newValue)); - _defaultValueField.RegisterValueChangedCallback( - evt => UpdateVisibleField(evt.newValue, _hasExplicitDefaultSetField.value)); - _defaultValueField.bindingPath = nameof(ParameterConfig.defaultValue); - - _hasExplicitDefaultSetField.RegisterValueChangedCallback( - evt => UpdateVisibleField(_defaultValueField.value, evt.newValue)); - _hasExplicitDefaultSetField.bindingPath = nameof(ParameterConfig.hasExplicitDefaultValue); - - _visibleField = new TextField(); - _visibleField.RegisterValueChangedCallback(evt => - { - if (string.IsNullOrWhiteSpace(evt.newValue)) - { - _hasExplicitDefaultSetField.value = false; - _defaultValueField.value = 0; - } - else - { - _hasExplicitDefaultSetField.value = true; - _defaultValueField.value = float.Parse(evt.newValue, CultureInfo.InvariantCulture); - } - }); - - _defaultValueField.style.width = 0; - _defaultValueField.SetEnabled(false); - _hasExplicitDefaultSetField.style.width = 0; - _hasExplicitDefaultSetField.SetEnabled(false); - - _boolField.RegisterValueChangedCallback(evt => - { - if (evt.newValue == V_True) - _defaultValueField.value = 1; - else - _defaultValueField.value = 0; - - _hasExplicitDefaultSetField.value = evt.newValue != V_None; - }); - - - style.flexDirection = FlexDirection.Row; - - Add(_visibleField); - Add(_boolField); Add(_defaultValueField); - Add(_hasExplicitDefaultSetField); + Add(_hasExplicitDefaultValueField); + Add(_numberField); + Add(_boolField); } - public void ManualBindProperty(SerializedProperty property) + public void OnUpdateSyncType(ParameterSyncType syncType) { - _defaultValueField.BindProperty(property); - _hasExplicitDefaultSetField.BindProperty(property); + _syncType = syncType; + + if (syncType != ParameterSyncType.Bool) + { + _numberField.style.display = DisplayStyle.Flex; + _boolField.style.display = DisplayStyle.None; + OnUpdateNumberValue(_numberField.value); + } + else + { + _numberField.style.display = DisplayStyle.None; + _boolField.style.display = DisplayStyle.Flex; + OnUpdateBoolValue(_boolField.value); + } } - + + private void OnUpdateNumberValue(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + _defaultValueField.value = 0; + _hasExplicitDefaultValueField.value = false; + } + else if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed) + && !float.IsNaN(parsed) + && !float.IsInfinity(parsed)) + { + _defaultValueField.value = _syncType switch + { + ParameterSyncType.Int => Mathf.FloorToInt(Mathf.Clamp(parsed, 0, 255)), + ParameterSyncType.Float => Mathf.Clamp(parsed, -1, 1), + ParameterSyncType.Bool => parsed != 0 ? 1 : 0, + _ => parsed, + }; + _hasExplicitDefaultValueField.value = true; + } + + UpdateVisibleField(_defaultValueField.value, _hasExplicitDefaultValueField.value); + } + + private void OnUpdateBoolValue(string value) + { + _defaultValueField.value = value == V_True ? 1 : 0; + _hasExplicitDefaultValueField.value = value != V_None; + + UpdateVisibleField(_defaultValueField.value, _hasExplicitDefaultValueField.value); + } + private void UpdateVisibleField(float value, bool hasExplicitValue) { - if (Mathf.Abs(value) > 0.0000001) + if (hasExplicitValue || Mathf.Abs(value) > 0.0000001) { - hasExplicitValue = true; + _numberField.SetValueWithoutNotify(value.ToString(CultureInfo.InvariantCulture)); + _boolField.SetValueWithoutNotify(value != 0 ? V_True : V_False); } - - var str = hasExplicitValue ? value.ToString(CultureInfo.InvariantCulture) : ""; - _visibleField.SetValueWithoutNotify(str); - - string boolStr; - if (!hasExplicitValue) - boolStr = V_None; - else if (value > 0.5) - boolStr = V_True; else - boolStr = V_False; - - _boolField.SetValueWithoutNotify(boolStr); + { + _numberField.SetValueWithoutNotify(string.Empty); + _boolField.SetValueWithoutNotify(V_None); + } } } -} \ No newline at end of file +} diff --git a/Editor/Inspector/Parameters/ParameterConfigDrawer.cs b/Editor/Inspector/Parameters/ParameterConfigDrawer.cs index 32b9ddf2..09bff39d 100644 --- a/Editor/Inspector/Parameters/ParameterConfigDrawer.cs +++ b/Editor/Inspector/Parameters/ParameterConfigDrawer.cs @@ -20,14 +20,14 @@ namespace nadena.dev.modular_avatar.core.editor.Parameters Localization.UI.Localize(root); root.styleSheets.Add(uss); - var type_field = root.Q("f-type"); - - var f_sync_type = root.Q("f-sync-type"); + var f_type = root.Q("f-type"); + var f_sync_type = root.Q("f-sync-type"); + var f_is_prefix = root.Q("f-is-prefix"); SetupPairedDropdownField( root, - type_field, + f_type, f_sync_type, - root.Q("f-is-prefix"), + f_is_prefix, ("Bool", "False", "params.syncmode.Bool"), ("Float", "False", "params.syncmode.Float"), ("Int", "False", "params.syncmode.Int"), @@ -35,15 +35,9 @@ namespace nadena.dev.modular_avatar.core.editor.Parameters (null, "True", "params.syncmode.PhysBonesPrefix") ); - f_sync_type.Q().RegisterValueChangedCallback(evt => - { - var is_anim_only = evt.newValue == "Not Synced"; - - if (is_anim_only) - root.AddToClassList("st-anim-only"); - else - root.RemoveFromClassList("st-anim-only"); - }); + var f_default = root.Q(); + f_default.OnUpdateSyncType((ParameterSyncType)f_sync_type.index); + f_sync_type.RegisterValueChangedCallback(evt => f_default.OnUpdateSyncType((ParameterSyncType)f_sync_type.index)); var f_synced = root.Q("f-synced"); var f_local_only = root.Q("f-local-only"); diff --git a/Editor/Inspector/Parameters/Parameters.uss b/Editor/Inspector/Parameters/Parameters.uss index 5449835c..4d243bb1 100644 --- a/Editor/Inspector/Parameters/Parameters.uss +++ b/Editor/Inspector/Parameters/Parameters.uss @@ -30,7 +30,7 @@ display: none; } -.st-anim-only .st-anim-only__hide { +.st-ty-Not-Synced .st-anim-only__hide { display: none; } @@ -79,28 +79,12 @@ margin: 0; } -DefaultValueField TextField { - display: flex; +DefaultValueField > * { width: 60px; height: 100%; margin: 0; } -DefaultValueField DropdownField { - display: none; - width: 60px; - height: 100%; - margin: 0; -} - -.st-ty-Bool DefaultValueField TextField { - display: none; -} - -.st-ty-Bool DefaultValueField DropdownField { - display: flex; -} - #f-local-only { display: none; } diff --git a/Editor/Inspector/ShapeChanger/ChangedShapeEditor.cs b/Editor/Inspector/ShapeChanger/ChangedShapeEditor.cs index 8b17c58a..bb3c84cd 100644 --- a/Editor/Inspector/ShapeChanger/ChangedShapeEditor.cs +++ b/Editor/Inspector/ShapeChanger/ChangedShapeEditor.cs @@ -64,7 +64,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger .Select(x => mesh.GetBlendShapeName(x)) .ToList(); } - catch (MissingComponentException e) + catch (MissingComponentException) { shapeNames = null; } diff --git a/Editor/Inspector/ShapeChanger/ShapeChangerEditor.cs b/Editor/Inspector/ShapeChanger/ShapeChangerEditor.cs index 31fc18e4..9bec32a3 100644 --- a/Editor/Inspector/ShapeChanger/ShapeChangerEditor.cs +++ b/Editor/Inspector/ShapeChanger/ShapeChangerEditor.cs @@ -19,11 +19,12 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger [SerializeField] private StyleSheet uss; [SerializeField] private VisualTreeAsset uxml; + private DragAndDropManipulator _dragAndDropManipulator; private BlendshapeSelectWindow _window; protected override void OnInnerInspectorGUI() { - throw new NotImplementedException(); + EditorGUILayout.HelpBox("Unable to show override changes", MessageType.Info); } protected override VisualElement CreateInnerInspectorGUI() @@ -41,6 +42,8 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger listView.showBoundCollectionSize = false; listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight; + _dragAndDropManipulator = new DragAndDropManipulator(root.Q("group-box"), target as ModularAvatarShapeChanger); + // The Add button callback isn't exposed publicly for some reason... var field_addButton = typeof(BaseListView).GetField("m_AddButton", NonPublic | Instance); var addButton = (Button)field_addButton.GetValue(listView); @@ -50,6 +53,41 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger return root; } + private void OnEnable() + { + if (_dragAndDropManipulator != null) + _dragAndDropManipulator.TargetComponent = target as ModularAvatarShapeChanger; + } + + private class DragAndDropManipulator : DragAndDropManipulator + { + public DragAndDropManipulator(VisualElement targetElement, ModularAvatarShapeChanger targetComponent) + : base(targetElement, targetComponent) { } + + protected override bool FilterGameObject(GameObject obj) + { + if (obj.TryGetComponent(out var smr)) + { + return smr.sharedMesh != null && smr.sharedMesh.blendShapeCount > 0; + } + return false; + } + + protected override void AddObjectReferences(AvatarObjectReference[] references) + { + Undo.RecordObject(TargetComponent, "Add Changed Shapes"); + + foreach (var reference in references) + { + var changedShape = new ChangedShape { Object = reference, ShapeName = string.Empty }; + TargetComponent.Shapes.Add(changedShape); + } + + EditorUtility.SetDirty(TargetComponent); + PrefabUtility.RecordPrefabInstancePropertyModifications(TargetComponent); + } + } + private void OnDisable() { if (_window != null) DestroyImmediate(_window); diff --git a/Editor/Inspector/ShapeChanger/ShapeChangerStyles.uss b/Editor/Inspector/ShapeChanger/ShapeChangerStyles.uss index adff445e..e74734ff 100644 --- a/Editor/Inspector/ShapeChanger/ShapeChangerStyles.uss +++ b/Editor/Inspector/ShapeChanger/ShapeChangerStyles.uss @@ -68,3 +68,13 @@ .change-type-delete #f-value-delete { display: flex; } + +.drop-area--drag-active { + background-color: rgba(0, 127, 255, 0.2); +} + +.drop-area--drag-active .unity-scroll-view, +.drop-area--drag-active .unity-list-view__footer, +.drop-area--drag-active .unity-list-view__reorderable-item { + background-color: rgba(0, 0, 0, 0.0); +} diff --git a/Editor/Localization/en-US.json b/Editor/Localization/en-US.json index 812970c0..07a5a565 100644 --- a/Editor/Localization/en-US.json +++ b/Editor/Localization/en-US.json @@ -86,6 +86,7 @@ "merge_armature.lockmode.bidirectional.body": "The base armature and the merged armature will always have the same position. This is useful when creating animations that are meant to target the base armature. In order to activate this, your armatures must already be in the exact same position.", "merge_armature.reset_pos": "Reset position to base avatar", "merge_armature.reset_pos.info": "This command will force the position of all bones in the outfit to match that of the base avatar. This can be helpful as a starting point for installing outfits not set up for your current avatar.", + "merge_armature.reset_pos.convert_atpose": "Convert A-Pose/T-Pose to match base avatar", "merge_armature.reset_pos.adjust_rotation": "Also set rotation to base avatar", "merge_armature.reset_pos.adjust_scale": "Also set local scale to base avatar", "merge_armature.reset_pos.execute": "Do it!", @@ -99,7 +100,7 @@ "worldfixed.quest": "This component is not compatible with Android builds and will have no effect.", "worldfixed.normal": "This object will be fixed to world unless you fixed to avatar with constraint.", "fpvisible.normal": "This object will be visible in your first person view.", - "fpvisible.NotUnderHead": "This component has no effect when not placed under the head bone.", + "fpvisible.NotUnderHead": "This component has no effect when not placed under the head bone.\nIf this will be placed under the head bone through Bone Proxy etc., this warning can be ignored.", "fpvisible.quest": "This component is not compatible with Android builds and will have no effect.", "fpvisible.InPhysBoneChain": "This object is controlled by a Physics Bone chain and cannot be made visible in first person safely. Select the start of the chain instead.", "blendshape.mesh": "Mesh", @@ -149,6 +150,8 @@ "error.rename_params.default_value_conflict:hint": "To avoid unpredictable behavior, leave the default value field blank in all but on MA Parameters component. If multiple values are present, Modular Avatar will select the first default value specified in the hierarchy order.", "error.replace_object.null_target": "[MA-0008] No target specified", "error.replace_object.null_target:hint": "Replace object needs a target object to replace. Try setting one.", + "error.replace_object.replacing_replacement": "[MA-0009] The same target object cannot be specified in multiple Replace Object components", + "error.replace_object.parent_of_target": "[MA-0010] The target object cannot be a parent of this object", "validation.blendshape_sync.no_local_renderer": "[MA-1000] No renderer found on this object", "validation.blendshape_sync.no_local_renderer:hint": "Blendshape Sync acts on a Skinned Mesh Renderer on the same GameObject. Did you attach it to the right object?", "validation.blendshape_sync.no_local_mesh": "[MA-1001] No mesh found on the renderer on this object", @@ -240,7 +243,7 @@ "setup_outfit.err.no_avatar_descriptor": "No avatar descriptor found in {0}'s parents. Make sure your outfit is placed inside your avatar.", "setup_outfit.err.no_animator": "Your avatar does not have an Animator component.", "setup_outfit.err.no_hips": "Your avatar does not have a Hips bone. Setup Outfit only works on humanoid avatars.", - "setup_outfit.err.no_outfit_hips": "Unable to identify the Hips object for the outfit. Searched for objects containing the following names:", + "setup_outfit.err.no_outfit_hips": "Unable to identify the Hips object for the outfit. Searched for objects containing the following names(case-insensitive):", "move_independently.group-header": "Objects to move together", "scale_adjuster.scale": "Scale adjustment", "scale_adjuster.adjust_children": "Adjust position of child objects", @@ -281,4 +284,4 @@ "ro_sim.effect_group.rule_inverted": "This rule is inverted", "ro_sim.effect_group.rule_inverted.tooltip": "This rule will be applied when one of its conditions is NOT met", "ro_sim.effect_group.conditions": "Conditions" -} \ No newline at end of file +} diff --git a/Editor/Localization/ja-JP.json b/Editor/Localization/ja-JP.json index 96f1ccfc..ad1378f4 100644 --- a/Editor/Localization/ja-JP.json +++ b/Editor/Localization/ja-JP.json @@ -9,7 +9,7 @@ "menuinstall.showcontents": "メニュー内容を表示", "menuinstall.showcontents.notselected": "メニューが選択されていません", "menuinstall.devoptions": "プレハブ開発者向け設定", - "menuinstall.menu_icon_too_large": "メニューに設定されているアイコンが256ピクセルより大きすぎます。", + "menuinstall.menu_icon_too_large": "メニューに設定されているアイコンが256x256より大きいです。", "menuinstall.menu_icon_uncompressed": "メニューに設定されているアイコンが圧縮設定されていません。", "menuinstall.srcmenu": "インストールされるメニュー", "params.syncmode.NotSynced": "Animatorのみ", @@ -31,32 +31,32 @@ "merge_parameter.ui.name": "パラメーター名", "merge_parameter.ui.prefix": "PhysBone 接頭辞", "merge_parameter.ui.remapTo": "名前を変更", - "merge_parameter.ui.remapTo.tooltip": "ここに新しい名前を入れることで、このパラメーターの名前を変更できます。これで名前かぶりを回避したり、あるいはあえて複数のギミックを連動できます。", + "merge_parameter.ui.remapTo.tooltip": "ここに新しい名前を入れることで、このパラメーターの名前を変更できます。これで名前かぶりを回避したり、複数のギミックを連動させたりすることができます。", "merge_parameter.ui.remapTo.automatic": "(自動的に設定)", "merge_parameter.ui.defaultValue": "初期値", - "merge_parameter.ui.defaultValue.tooltip": "アバターがリセット、または最初に着た時にこの値が採用されます。", + "merge_parameter.ui.defaultValue.tooltip": "アバターをリセットした時、または最初に着た時にこの値が採用されます。", "merge_parameter.ui.saved": "保存する", - "merge_parameter.ui.saved.tooltip": "保存されたパラメーターは、アバター変更やワールド移動で保持されます", + "merge_parameter.ui.saved.tooltip": "保存されたパラメーターは、アバター変更やワールド移動をしても保持されます", "merge_parameter.ui.internalParameter": "自動リネーム", "merge_parameter.ui.internalParameter.tooltip": "有効にすると、名前かぶりを回避するために自動的に名前を変更します", "merge_parameter.ui.isPrefix": "PhysBone 接頭辞", "merge_parameter.ui.syncType": "パラメーター型", "merge_parameter.ui.synced": "同期する", - "merge_parameter.ui.synced.tooltip": "有効にすると、ネットワーク上同期されます", + "merge_parameter.ui.synced.tooltip": "有効にすると、このパラメーターは同期されます", "merge_parameter.ui.unregistered_foldout": "未登録パラメーター", "merge_parameter.ui.add_button": "追加", "merge_parameter.ui.details": "パラメーターの詳細設定", - "merge_parameter.ui.overrideAnimatorDefaults": "アニメーターの初期値を設定", + "merge_parameter.ui.overrideAnimatorDefaults": "アニメーターでの初期値を設定", "merge_armature.merge_target": "統合先", "merge_armature.merge_target.tooltip": "このオブジェクトを統合先のアーマチュアに統合します", "merge_armature.prefix": "接頭辞", - "merge_armature.prefix.tooltip": "マージするボーンに付いている接頭辞", + "merge_armature.prefix.tooltip": "統合されるボーンに付いている接頭辞", "merge_armature.suffix": "接尾辞", - "merge_armature.suffix.tooltip": "マージするボーンに付いている接尾辞", + "merge_armature.suffix.tooltip": "統合されるボーンに付いている接尾辞", "merge_armature.locked": "位置を固定", - "merge_armature.locked.tooltip": "このオブジェクトのボーンを統合先のボーンに常に相互的に位置を合わせる。アニメーション制作向け", + "merge_armature.locked.tooltip": "このオブジェクトのボーンと統合先のボーンの位置を常に合わせます。アニメーション作成時に便利です。", "merge_armature.adjust_names": "ボーン名を統合先に合わせる", - "merge_armature.adjust_names.tooltip": "統合先のボーン名に合わせて、衣装のボーン名を合わせて変更します。統合先アバターに非対応の衣装導入向け機能です。", + "merge_armature.adjust_names.tooltip": "衣装のボーン名をアバターのボーン名に合わせて変更します。アバターに非対応の衣装を導入する時に便利です。", "merge_armature.mangle_names": "名前かぶりを回避", "merge_armature.mangle_names.tooltip": "ほかのアセットとの名前かぶりを裂けるため、新規ボーンの名前を自動で変更する", "path_mode.Relative": "相対的(このオブジェクトからのパスを使用)", @@ -66,12 +66,12 @@ "merge_animator.delete_attached_animator": "付属アニメーターを削除", "merge_animator.delete_attached_animator.tooltip": "統合後、このオブジェクトについているアニメーターを削除します", "merge_animator.path_mode": "パスモード", - "merge_animator.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトについているアニメーターでアニメーション編集できます", + "merge_animator.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトにつけたアニメーターでアニメーションを編集することができます。", "merge_animator.match_avatar_write_defaults": "アバターのWrite Defaults設定に合わせる", "merge_animator.match_avatar_write_defaults.tooltip": "アバターの該当アニメーターのWrite Defaults設定に合わせます。アバター側の設定が矛盾する場合は、統合されるアニメーターのWD値がそのまま採用されます。", "merge_animator.relative_path_root": "相対的パスのルート", "merge_animator.relative_path_root.tooltip": "相対的パスはこのオブジェクトを基準に解釈されます。指定がない場合は、このコンポーネントがついているオブジェクトを基準とします。", - "merge_animator.layer_priority": "レイヤー優先度", + "merge_animator.layer_priority": "レイヤー統合優先度", "merge_animator.layer_priority.tooltip": "アニメーターにレイヤーが統合される順番を制御します。低い値から高い値の順に統合されます。マイナスの場合は元々のAvatar Descriptorについているコントローラーより前に統合され、ゼロ以上の場合はそのあとに統合されます。", "merge_armature.lockmode": "位置追従モード", "merge_armature.lockmode.not_locked.title": "追従なし", @@ -81,28 +81,29 @@ "merge_armature.lockmode.bidirectional.title": "アバター <=====> オブジェクト (双方向)", "merge_armature.lockmode.bidirectional.body": "アバターと統合されるアーマチュアは常に同じ位置になります。元のアバターを操作するアニメーションを作る時に便利かもしれません。有効にするためには、統合されるアーマチュアの位置を統合先と同じにしておく必要があります。", "merge_armature.reset_pos": "位置を元アバターに合わせてリセット", - "merge_armature.reset_pos.info": "このコマンドは、衣装のボーンの位置をアバターのボーンの位置に合わせます。非対応衣装を導入するとき、アバウトに合わせるために便利です。", + "merge_armature.reset_pos.info": "衣装のボーンの位置をアバターのボーンの位置に合わせます。非対応衣装を導入する際、アバウトに位置を合わせるのに便利です。", + "merge_armature.reset_pos.convert_atpose": "Aポーズ/Tポーズを合わせる", "merge_armature.reset_pos.adjust_rotation": "回転も合わせる", "merge_armature.reset_pos.adjust_scale": "スケールも合わせる", "merge_armature.reset_pos.execute": "実行", "merge_armature.reset_pos.heuristic_scale": "衣装の全体的なスケールをアバターに合わせる", - "merge_armature.reset_pos.heuristic_scale.tooltip": "腕の長さを参考に、衣装全体のスケールをアバターに合わせます。非対応衣装を導入するときは推奨です。", + "merge_armature.reset_pos.heuristic_scale.tooltip": "腕の長さを参考に、衣装全体のスケールをアバターに合わせます。非対応衣装を導入する時にお勧めです。", "merge_blend_tree.blend_tree": "ブレンドツリー", "merge_blend_tree.path_mode": "パスモード", - "merge_blend_tree.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトについているアニメーターでアニメーション編集できます", + "merge_blend_tree.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトにつけたアニメーターでアニメーションを編集することができます。", "merge_blend_tree.relative_path_root": "相対的パスのルート", "merge_blend_tree.relative_path_root.tooltip": "相対的パスはこのオブジェクトを基準に解釈されます。指定がない場合は、このコンポーネントがついているオブジェクトを基準とします。", - "worldfixed.quest": "このコンポーネントはアンドロイドビルド非対応のため無効となっています。", + "worldfixed.quest": "このコンポーネントはアンドロイドビルドに非対応であるため、無効となっています。", "worldfixed.normal": "このオブジェクトはConstraint等でアバターに追従させない限りワールドに固定されます。", - "fpvisible.normal": "このオブジェクトは一人視点で表示されます。", - "fpvisible.NotUnderHead": "このコンポーネントはヘッドボーン外では効果がありません。", - "fpvisible.quest": "このコンポーネントはアンドロイドビルド非対応のため無効となっています。", - "fpvisible.InPhysBoneChain": "このオブジェクトはPhysBoneに制御されているため、一人視点で表示できません。PhysBoneの始点を指定してください。", + "fpvisible.normal": "このオブジェクトは一人称視点で表示されます。", + "fpvisible.NotUnderHead": "このコンポーネントは頭ボーンの配下でないと効果がありません。\n(Bone Proxyなどで頭ボーンの配下に配置される場合は効果あります)", + "fpvisible.quest": "このコンポーネントはアンドロイドビルドに非対応であるため、無効となっています。", + "fpvisible.InPhysBoneChain": "このオブジェクトはPhysBoneで制御されているため、一人称視点でうまく表示させることができません。PhysBoneの始点を指定してください。", "blendshape.mesh": "メッシュ", - "blendshape.source": "元メッシュのブレンドシェープ", - "blendshape.target": "このメッシュのブレンドシェープ", - "hint.not_in_avatar": "このコンポーネントが正しく動作するには、アバター内に配置する必要があります。", - "boneproxy.err.MovingTarget": "他のモジュラーアバターコンポーネントで移動されるオブジェクトを指定できません。", + "blendshape.source": "元メッシュのブレンドシェイプ", + "blendshape.target": "このメッシュのブレンドシェイプ", + "hint.not_in_avatar": "このコンポーネントを正しく動作させるには、アバター内に配置する必要があります。", + "boneproxy.err.MovingTarget": "他のモジュラーアバターコンポーネントで移動されるオブジェクトは指定できません。", "boneproxy.err.NotInAvatar": "アバター内のオブジェクトを指定してください。", "boneproxy.attachment": "配置モード", "boneproxy.attachment.AsChildAtRoot": "子として・ルートに配置", @@ -111,28 +112,28 @@ "boneproxy.attachment.AsChildKeepRotation": "子として・ワールド向きを維持", "mesh_settings.header_probe_anchor": "Anchor Override 設定", "mesh_settings.inherit_probe_anchor": "設定モード", - "mesh_settings.probe_anchor": "アンカーオーバーライド", - "mesh_settings.probe_anchor.tooltip": "このオブジェクトとその子のレンダラーのAnchorOverrideを設定します。", + "mesh_settings.probe_anchor": "Anchor Override", + "mesh_settings.probe_anchor.tooltip": "このオブジェクトとその子のレンダラーのAnchor Overrideを設定します。", "mesh_settings.header_bounds": "Bounds 設定", "mesh_settings.inherit_bounds": "設定モード", "mesh_settings.root_bone": "ルートボーン", - "mesh_settings.root_bone.tooltip": "このオブジェクトとその子のメッシュで設定されるルートボーン。メッシュのバウンズを計算するための参照点として使用されます。", - "mesh_settings.bounds": "バウンズ", + "mesh_settings.root_bone.tooltip": "このオブジェクトとその子のメッシュで設定されるルートボーン。メッシュのBoundsを計算する際の基準として使用されます。", + "mesh_settings.bounds": "Bounds", "mesh_settings.bounds.tooltip": "このオブジェクトとその子のメッシュで設定されるバウンズ。画面外のメッシュのレンダリングを省略するかどうかを決定するために使用されます。", "mesh_settings.inherit_mode.Inherit": "継承", "mesh_settings.inherit_mode.Set": "設定", "mesh_settings.inherit_mode.DontSet": "設定しない(メッシュ本体の設定のまま)", - "mesh_settings.inherit_mode.SetOrInherit": "親が指定されてる時は継承、または設定", + "mesh_settings.inherit_mode.SetOrInherit": "親で指定されている時は継承、それ以外では設定", "pb_blocker.help": "このオブジェクトは親のPhysBoneから影響を受けなくなります。", - "hint.bad_vrcsdk": "使用中のVRCSDKのバージョンとは互換性がありません。\n\nVRCSDKを更新してみてください。それでもだめでしたら、Modular Avatarにも最新版が出てないかチェックしてください。", + "hint.bad_vrcsdk": "使用中のVRCSDKのバージョンとは互換性がありません。\n\nVRCSDKを更新してみてください。それでも駄目な場合、Modular Avatarにも最新版が出ていないか確認してみてください。", "error.stack_trace": "スタックトレース(バグを報告する時は必ず添付してください!)", "error.merge_armature.circular_dependency": "[MA-0001] Merge Armatureに循環参照があります", - "error.merge_armature.circular_dependency:description": "Merge Armature コンポーネントは、自分自身、または自分の子をマージターゲットとして参照しています。", - "error.merge_armature.circular_dependency:hint": "Merge Armature は通常、ターゲットフィールドにアバター本体の Armature オブジェクトを指定する必要があります。衣装自体を指定しないでください。", - "error.merge_armature.physbone_on_humanoid_bone": "[MA-0002] ヒューマノイドボーンにPhysBoneコンポーネントを検出", - "error.merge_armature.physbone_on_humanoid_bone:hint": "一部のヒューマノイドボーンは、PhysBonesによって制御されています。 マージ対象の対応するヒューマノイドボーンとは位置が異なるため、適切にマージすることはできません。マージするにはヒューマノイドボーンのPhysBonesを取り除く必要があります。", + "error.merge_armature.circular_dependency:description": "Merge Armatureコンポーネントの統合先として、自分自身、または自分の子が参照されています。", + "error.merge_armature.circular_dependency:hint": "通常、Merge Armatureは統合先としてアバター本体のArmatureオブジェクトを指定する必要があります。衣装自体を指定しないように注意してください。", + "error.merge_armature.physbone_on_humanoid_bone": "[MA-0002] HumanoidボーンにPhysBoneコンポーネントがついています。", + "error.merge_armature.physbone_on_humanoid_bone:hint": "一部のHumanoidボーンがPhysBoneによって制御されています。対応する統合先のHumanoidボーンと位置が異なるため、適切に統合することができません。統合するにはHumanoidボーンについているPhysBoneを取り除く必要があります。", "error.merge_blend_tree.missing_tree": "[MA-0009] ブレンドツリーが指定されていません", - "error.merge_blend_tree.missing_tree:hint": "Merge Blend Treeが動作するには、どのブレンドツリーを統合するかを指定する必要があります。「ブレンドツリー」欄を設定してみてください。", + "error.merge_blend_tree.missing_tree:hint": "Merge Blend Treeが動作するには、どのブレンドツリーを統合するか指定する必要があります。「ブレンドツリー」欄を設定しているかご確認ください。", "error.internal_error": "[MA-9999] 内部エラーが発生しました:{0}\n以下のオブジェクトの処理中に発生しました:", "error.merge_animator.param_type_mismatch": "[MA-0003] パラメータの型が競合しています", "error.merge_animator.param_type_mismatch:description": "パラメータ {0} には複数の型が指定されています: {1} != {2}", @@ -142,37 +143,39 @@ "error.rename_params.type_conflict:description": "パラメータ {0} には複数の型が指定されています: {1} != {2}", "error.rename_params.default_value_conflict": "[MA-0007] 初期値の競合", "error.rename_params.default_value_conflict:description": "パラメータ {0} には複数の初期値が指定されています: {1} != {2}", - "error.rename_params.default_value_conflict:hint": "予測不可能な動作を避けるために、MAパラメータコンポーネントのデフォルト値フィールドはパラメーター名毎に一個のコンポーネント以外空白のままにしてください。 複数の値が存在する場合、Modular Avatarは階層順に指定された最初のデフォルト値を選択します。", - "error.replace_object.null_target": "[MA-0008] ターゲットが指定されていません", - "error.replace_object.null_target:hint": "Replace Object には置き換えるべきターゲットオブジェクトが必要です。設定してみてください。", - "validation.blendshape_sync.no_local_renderer": "[MA-1000] このオブジェクトにはSkinnedMeshRendererがありません。", - "validation.blendshape_sync.no_local_renderer:hint": "Blendshape Syncは同じGameObject上のSkinned Mesh Rendererに作用します。正しいオブジェクトに追加しましたか?", - "validation.blendshape_sync.no_local_mesh": "[MA-1001] このオブジェクトにはSkinnedMeshRendererがありますが、メッシュがありません。", + "error.rename_params.default_value_conflict:hint": "予測不可能な動作を避けるため、MA Parametersコンポーネントの初期値フィールドはパラメーター名毎に1つだけしか指定しないようにし、他のコンポーネントでは空白のままにしてください。複数の値が存在する場合、Modular Avatarは階層順で最初に指定された初期値を採用します。", + "error.replace_object.null_target": "[MA-0008] 置き換え先が指定されていません", + "error.replace_object.null_target:hint": "Replace Objectは置き換え先のオブジェクトを指定する必要があります。", + "error.replace_object.replacing_replacement": "[MA-0009] 複数のReplace Objectコンポーネントで、同じ置き換え先を指定できません", + "error.replace_object.parent_of_target": "[MA-0010] このオブジェクトの親を置き換え先に指定できません", + "validation.blendshape_sync.no_local_renderer": "[MA-1000] このオブジェクトにはSkinned Mesh Rendererがありません。", + "validation.blendshape_sync.no_local_renderer:hint": "Blendshape Syncは同じGameObject上のSkinned Mesh Rendererに作用します。コンポーネントが正しいオブジェクトに追加されているか確認してください。", + "validation.blendshape_sync.no_local_mesh": "[MA-1001] このオブジェクトにはSkinned Mesh Rendererがありますが、メッシュがありません。", "validation.blendshape_sync.no_local_mesh:hint": "このオブジェクトの Skinned Mesh Renderer の設定が壊れている可能性があります。 元のプレハブまたはFBXからオブジェクトを再作成してみてください。", - "validation.blendshape_sync.no_bindings": "[MA-1002] このBlendshapeSyncにはバインドが設定されていません。", + "validation.blendshape_sync.no_bindings": "[MA-1002] このBlendshape Syncにはバインドが設定されていません。", "validation.blendshape_sync.no_bindings:hint": "Blendshape Syncは、どのブレンドシェイプを同期するかを知る必要があります。追加するには、「+」ボタンをクリックしてください。", - "validation.blendshape_sync.missing_local_shape": "[MA-1003] 同期先のメッシュに該当するブレンドシェープ「{0}」がありません。", - "validation.blendshape_sync.missing_local_shape:description": "ローカルブレンドシェイプがありません: {0}", - "validation.blendshape_sync.missing_local_shape:hint": "ターゲットオブジェクトから値を受け取るように設定されたブレンドシェイプがありません。赤で示されているブレンドシェイプ名を変更してみてください。", - "validation.blendshape_sync.missing_target_shape": "[MA-1004] 同期先メッシュにブレンドシェープ「{0}」が見つかりません", - "validation.blendshape_sync.missing_target_shape:description": "ターゲットブレンドシェイプがありません: {0}", - "validation.blendshape_sync.missing_target_shape:hint": "ローカルオブジェクトに値を「送る」ように設定されたブレンドシェイプがありません。赤で示されているブレンドシェイプ名を変更してみてください。", - "validation.blendshape_sync.no_target": "[MA-1005] このBlendshapeSyncには同期元が設定されていないバインドがあります。", - "validation.blendshape_sync.no_target:hint": "どのオブジェクトからBlendshapeを同期するかを教える必要があります。メッシュを設定してみてください。", - "validation.blendshape_sync.missing_target_renderer": "[MA-1006] 同期元のオブジェクトにはSkinnedMeshRendererがありません。", - "validation.blendshape_sync.missing_target_renderer:hint": "Blendshape Syncは、対象オブジェクトのSkinned Mesh Rendererからブレンド形状の値を受け取ります。正しいオブジェクトに追加しましたか?", - "validation.blendshape_sync.missing_target_mesh": "[MA-1007] 同期元のオブジェクトにはSkinnedMeshRendererがありますが、メッシュがありません。", - "validation.blendshape_sync.missing_target_mesh:hint": "ターゲットオブジェクトの Skinned Mesh Renderer の設定が壊れている可能性があります。 元のプレハブまたはFBXからオブジェクトを再作成してみてください。", - "validation.bone_proxy.no_target": "[MA-1100] ターゲットオブジェクトが未設定、もしくは存在しません。", - "validation.bone_proxy.no_target:hint": "ボーンプロキシがどのオブジェクトに追尾するかを知る必要があります。追尾すべきオブジェクトをターゲットフィールドに設定してみてください。", + "validation.blendshape_sync.missing_local_shape": "[MA-1003] 同期先のメッシュにブレンドシェイプ「{0}」がありません。", + "validation.blendshape_sync.missing_local_shape:description": "ブレンドシェイプ「{0}」がありません。", + "validation.blendshape_sync.missing_local_shape:hint": "値を「受け取る」ように設定されたブレンドシェイプが同期先のメッシュに存在しません。赤で示されているブレンドシェイプ名を変更してみてください。", + "validation.blendshape_sync.missing_target_shape": "[MA-1004] 同期元のメッシュにブレンドシェイプ「{0}」がありません。", + "validation.blendshape_sync.missing_target_shape:description": "ブレンドシェイプ「{0}」がありません。", + "validation.blendshape_sync.missing_target_shape:hint": "値を「送る」ように設定されたブレンドシェイプが同期元のメッシュに存在しません。赤で示されているブレンドシェイプ名を変更してみてください。", + "validation.blendshape_sync.no_target": "[MA-1005] このBlendshape Syncには同期元が設定されていないバインドがあります。", + "validation.blendshape_sync.no_target:hint": "どのオブジェクトからBlendshapeを同期するか指定する必要があります。メッシュを設定してください。", + "validation.blendshape_sync.missing_target_renderer": "[MA-1006] 同期元のオブジェクトにSkinned Mesh Rendererがありません。", + "validation.blendshape_sync.missing_target_renderer:hint": "Blendshape Syncは、同期元のSkinned Mesh Rendererからブレンドシェイプの値を受け取ります。コンポーネントが正しいオブジェクトに追加されているか確認してください。", + "validation.blendshape_sync.missing_target_mesh": "[MA-1007] 同期元のオブジェクトにはSkinned Mesh Rendererがありますが、メッシュがありません。", + "validation.blendshape_sync.missing_target_mesh:hint": "同期元のオブジェクトの Skinned Mesh Renderer の設定が壊れている可能性があります。 元のプレハブまたはFBXからオブジェクトを再作成してみてください。", + "validation.bone_proxy.no_target": "[MA-1100] ターゲットオブジェクトが未設定であるか、存在しません。", + "validation.bone_proxy.no_target:hint": "Bone Proxyはどのオブジェクトに追従させるかを指定する必要があります。追従させたい対象のオブジェクトをターゲット欄に設定してみてください。", "validation.menu_installer.no_menu": "[MA-1200] インストールするメニューがありません。", "validation.menu_installer.no_menu:hint": "Menu Installer は、どのメニューをインストールするべきか設定する必要があります。 「Prefab開発者向けオプション」内の「インストールするメニュー」フィールドを設定するか、MA Menu Itemコンポーネントを追加してみてください。", - "validation.merge_animator.no_animator": "[MA-1300] Animator Controllerが設定されていません", - "validation.merge_animator.no_animator:hint": "Animatorをマージするには、どのアニメーターを統合するかを設定する必要があります。「統合されるアニメーター」を設定してみてください。", - "validation.merge_armature.no_target": "[MA-1400] 統合先が未設定、もしくは存在しません。", + "validation.merge_animator.no_animator": "[MA-1300] Animator Controllerが未設定であるか、存在しません。", + "validation.merge_animator.no_animator:hint": "どのアニメーターを統合するか設定する必要があります。「統合されるアニメーター」を設定してみてください。", + "validation.merge_armature.no_target": "[MA-1400] 統合先が未設定であるか、存在しません。", "validation.merge_armature.no_target:hint": "Merge Armatureはどこに統合するか設定する必要があります。「統合先」を設定してみてください。", - "validation.merge_armature.target_is_child": "[MA-1500] 統合先をこのオブジェクトの子にすることができません", - "validation.merge_armature.target_is_child:hint": "Merge Armature は自身の子に統合できません。「統合先」を別のオブジェクトに設定してみてください。", + "validation.merge_armature.target_is_child": "[MA-1500] 統合先として自身の子を指定することはできません。", + "validation.merge_armature.target_is_child:hint": "Merge Armatureで自身の子に統合することはできません。「統合先」を別のオブジェクトに設定してみてください。", "submenu_source.Children": "子オブジェクトから生成", "submenu_source.MenuAsset": "Expressions Menu アセットを指定", "menuitem.showcontents": "メニュー内容を表示", @@ -182,27 +185,27 @@ "menuitem.prop.type": "タイプ", "menuitem.prop.type.tooltip": "この項目の種別", "menuitem.prop.value": "パラメーター値", - "menuitem.prop.value.tooltip": "この項目が操作されたとき、パラメーターが設定される値", + "menuitem.prop.value.tooltip": "この項目が操作されたとき、パラメーターに設定される値", + "menuitem.prop.automatic_value": "自動", + "menuitem.prop.automatic_value.tooltip": "かぶらない値を自動的に割り振る", "menuitem.prop.parameter": "パラメーター名", "menuitem.prop.label": "表示名", "menuitem.prop.submenu_asset": "サブメニューアセット", "menuitem.prop.submenu_asset.tooltip": "サブメニューとして引用するアセット", "menuitem.prop.submenu_source": "サブメニュー引用元", - "menuitem.prop.submenu_source.tooltip": "このサブメニューの内容をどこから引用するべきかを指定", + "menuitem.prop.submenu_source.tooltip": "このサブメニューの内容をどこから引用するべきか指定します。", "menuitem.prop.source_override": "引用元オブジェクト", - "menuitem.prop.source_override.tooltip": "指定した場合は、指定したオブジェクトの子をメニューの内容として指定します。指定されてない場合はこのオブジェクト直下の子を使用します。", + "menuitem.prop.source_override.tooltip": "指定した場合、指定したオブジェクトの子をメニューの内容として指定します。指定されてない場合はこのオブジェクト直下の子を使用します。", "menuitem.prop.is_default": "初期設定にする", - "menuitem.prop.is_default.tooltip": "ONの場合、アバター初期化の際にこのメニューアイテムを選択します", + "menuitem.prop.is_default.tooltip": "有効になっていると、アバター初期化の際にこのメニューアイテムを選択した状態にします。", "menuitem.prop.is_saved": "保存する", - "menuitem.prop.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動するときこの設定が保持されます。", + "menuitem.prop.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動をしてもこの設定が保持されます。", "menuitem.prop.is_synced": "同期する", - "menuitem.prop.is_synced.tooltip": "有効の場合はほかのプレイヤーに同期されます。", - "menuitem.prop.automatic_value": "自動", - "menuitem.prop.automatic_value.tooltip": "かぶらない値を自動的に割り振る", - "menuitem.param.rotation": "回転パラメーター名", - "menuitem.param.rotation.tooltip": "このメニューアイテムの回転に連動するべきパラメーター", + "menuitem.prop.is_synced.tooltip": "有効になっていると、メニューがほかのプレイヤーに同期されます。", + "menuitem.param.rotation": "ラジアルメニュー用パラメーター名", + "menuitem.param.rotation.tooltip": "このラジアルメニューの操作と連動するべきパラメーター", "menuitem.param.horizontal": "横パラメーター名", - "menuitem.param.horizontal.tooltip": "横操作に連動するパラメーター名", + "menuitem.param.horizontal.tooltip": "左右操作に連動するパラメーター名", "menuitem.param.vertical": "縦パラメーター名", "menuitem.param.vertical.tooltip": "上下操作に連動するパラメーター名", "menuitem.label.control_labels_and_params": "表示名・パラメーター", @@ -218,29 +221,29 @@ "control_group.foldout.actions": "アクション", "control_group.foldout.menu_items": "関連付けされたメニューアイテム", "control_group.is_saved": "保存する", - "control_group.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動するときこの設定が保持されます。", + "control_group.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動をしてもこの設定が保持されます。", "control_group.is_synced": "同期する", - "control_group.is_synced.tooltip": "有効の場合はほかのプレイヤーに同期されます。", + "control_group.is_synced.tooltip": "有効になっていると、メニューがほかのプレイヤーに同期されます。", "control_group.default_value": "初期値", "control_group.default_value.unset": "(どれも選択されない)", "animation_gen.duplicate_binding": "別々のコントロールグループから、同じパラメーターが操作されています。パラメーター:{0}", - "animation_gen.multiple_defaults": "同じコントロールグループに初期設定に指定されたメニューアイテムが複数あります。", + "animation_gen.multiple_defaults": "同じコントロールグループに初期設定にするとして指定されたメニューアイテムが複数あります。", "menuitem.misc.add_item": "メニューアイテムを追加", - "replace_object.target_object": "上書き先", - "setup_outfit.err.header.notarget": "Setup outfit の処理が失敗しました", - "setup_outfit.err.header": "Setup outfit が「{0}」を処理中に失敗しました。", + "replace_object.target_object": "置き換え先", + "setup_outfit.err.header.notarget": "Setup Outfit の処理に失敗しました", + "setup_outfit.err.header": "Setup Outfit が「{0}」を処理中に失敗しました。", "setup_outfit.err.unknown": "原因不明のエラーが発生しました。", "setup_outfit.err.no_selection": "オブジェクトが選択されていません。", - "setup_outfit.err.run_on_avatar_itself": "Setup Outfitはアバター自体ではなく、衣装のほうで実行してください。\n\nキメラアバターを作る場合は、中のほうのAvatar Descriptorを消して、衣装として扱ってください。", - "setup_outfit.err.multiple_avatar_descriptors": "「{0}」とその親に、複数のavatar descriptorを発見しました。\n\nキメラアバターを作る場合は、中のほうのAvatar Descriptorを消して、衣装として扱ってください。", - "setup_outfit.err.no_avatar_descriptor": "「{0}」の親に、avatar descriptorが見つかりませんでした。衣装のオブジェクトをアバターの中に配置してください。", + "setup_outfit.err.run_on_avatar_itself": "Setup Outfitはアバター自体ではなく、衣装の方で実行してください。\n\nキメラアバターを作る場合は、中の方のAvatar Descriptorを消して、衣装として扱ってください。", + "setup_outfit.err.multiple_avatar_descriptors": "「{0}」とその親で複数のAvatar Descriptorを発見しました。\n\nキメラアバターを作る場合は、中の方のAvatar Descriptorを消して、衣装として扱ってください。", + "setup_outfit.err.no_avatar_descriptor": "「{0}」の親にAvatar Descriptorが見つかりませんでした。衣装のオブジェクトをアバターの中に配置してください。", "setup_outfit.err.no_animator": "アバターにAnimatorコンポーネントがありません。", - "setup_outfit.err.no_hips": "アバターにHipsボーンがありません。なお、Setup Outfitはヒューマノイドアバター以外には対応していません。", - "setup_outfit.err.no_outfit_hips": "衣装のHipsボーンを発見できませんでした。以下の名前を含むボーンを探しました:", - "move_independently.group-header": "一緒に動かすオブジェクト", + "setup_outfit.err.no_hips": "アバターにHipsボーンがありません。なお、Setup OutfitはHumanoidアバター以外には対応していません。", + "setup_outfit.err.no_outfit_hips": "衣装のHipsボーンを発見できませんでした。アクセサリー等に対してSetup Outfitを試みた場合は、代わりにBone Proxyコンポーネントを使って配置してみてください。\n以下の名前を含むボーンを(大文字・小文字を区別つけずに)探しました:", + "move_independently.group-header": "同時に動かすオブジェクト", "scale_adjuster.scale": "Scale調整値", "scale_adjuster.adjust_children": "子オブジェクトの位置を調整", - "world_fixed_object.err.unsupported_platform": "World Fixed Objectがこのプラットフォームに対応していません", + "world_fixed_object.err.unsupported_platform": "World Fixed Objectはこのプラットフォームに対応していません。", "world_fixed_object.err.unsupported_platform:description": "World Fixed ObjectはAndroid向けビルドには対応していないため、動作しません。", "ma_info.param_usage_ui.header": "Expressions Parameter 使用状況", "ma_info.param_usage_ui.other_objects": "このアバター内の他のオブジェクト", @@ -250,8 +253,8 @@ "reactive_object.inverse": "条件を反転", "reactive_object.material-setter.set-to": "変更先のマテリアル ", "menuitem.misc.add_toggle": "トグルを追加", - "ro_sim.open_debugger_button": "リアクションデバッグツールを開く", - "ro_sim.window.title": "MA リアクションデバッグツール", + "ro_sim.open_debugger_button": "Reaction デバッガーを開く", + "ro_sim.window.title": "MA Reaction デバッガー", "ro_sim.header.inspecting": "表示中のオブジェクト", "ro_sim.header.clear_overrides": "すべてのオーバーライドを解除", "ro_sim.header.object_state": "オブジェクトのアクティブ状態", @@ -260,16 +263,17 @@ "ro_sim.header.override_gameobject_state": "GameObject のアクティブ状態をオーバーライド", "ro_sim.header.override_menuitem_state": "MenuItem の選択状態をオーバーライト", "ro_sim.affected_by.title": "以下のルールに影響されています", - "ro_sim.effect_group.controls_obj_state": "オブジェクトのアクティブ状態を設定する➡", + "ro_sim.effect_group.component": "Reactive Component", + "ro_sim.effect_group.controls_obj_state": "オブジェクトのアクティブ状態を次に設定する➡", "ro_sim.effect_group.target_component": "コンポーネント", "ro_sim.effect_group.target_component.tooltip": "Reactive Componentに影響されるコンポーネント", "ro_sim.effect_group.property": "プロパティ", "ro_sim.effect_group.property.tooltip": "設定されるプロパティ", "ro_sim.effect_group.value": "値", - "ro_sim.effect_group.value.tooltip": "上記 Reactive Component が活性状態の時に設定される値", + "ro_sim.effect_group.value.tooltip": "上記の Reactive Component がアクティブな時に設定される値", "ro_sim.effect_group.material": "マテリアル", - "ro_sim.effect_group.material.tooltip": "上記 Reactive Component が活性状態の時に設定されるマテリアル", - "ro_sim.effect_group.rule_inverted": "このルールの条件が反転されています", + "ro_sim.effect_group.material.tooltip": "上記の Reactive Component がアクティブな時に設定されるマテリアル", + "ro_sim.effect_group.rule_inverted": "このルールの条件は反転されています", "ro_sim.effect_group.rule_inverted.tooltip": "このルールは、いずれかの条件が満たされていない場合に適用されます", "ro_sim.effect_group.conditions": "条件" -} \ No newline at end of file +} diff --git a/Editor/Localization/ko-KR.json b/Editor/Localization/ko-KR.json index 6194b873..0278dbb7 100644 --- a/Editor/Localization/ko-KR.json +++ b/Editor/Localization/ko-KR.json @@ -96,7 +96,6 @@ "worldfixed.quest": "이 컴포넌트는 안드로이드 빌드를 대응하지 않습니다.", "worldfixed.normal": "이 오브젝트는 Constraint 을 사용하여 아바타에 고정하지 않는 이상 월드축에 고정됩니다.", "fpvisible.normal": "이 오브젝트는 일인칭 시점에서 보일 것입니다.", - "fpvisible.NotUnderHead": "Head 본(Bone) 내의 오브젝트가 아닌 경우 작동하지 않습니다.", "fpvisible.quest": "이 컴포넌트는 오큘러스 퀘스트 단독 버전과 호환되지 않으며 영향을 미치지 않습니다.", "fpvisible.InPhysBoneChain": "이 객체는 Physics Bone 체인에 의해 컨트롤되므로 체인의 세부적인 선택이 불가능합니다. 대신 체인의 시작 부분을 선택하세요.", "blendshape.mesh": "메시", @@ -144,4 +143,4 @@ "control_group.is_synced": "동기화됨", "setup_outfit.err.unknown": "알 수 없는 오류", "ma_info.param_usage_ui.other_objects": "이 아바타안의 다른 오브젝트들" -} \ No newline at end of file +} diff --git a/Editor/Localization/zh-Hans.json b/Editor/Localization/zh-Hans.json index ec0bce70..4b5d56a8 100644 --- a/Editor/Localization/zh-Hans.json +++ b/Editor/Localization/zh-Hans.json @@ -94,7 +94,6 @@ "worldfixed.quest": "此组件未生效,因为它与 Oculus Quest 不兼容。", "worldfixed.normal": "当前对象将会固定于世界,除非您使用约束将它绑定在 Avatar 内。", "fpvisible.normal": "当前对象将在第一人称视角中可见。", - "fpvisible.NotUnderHead": "此组件未生效,因为它需要放置在 Head 骨骼下。", "fpvisible.quest": "此组件未生效,因为它与 Oculus Quest 不兼容。", "fpvisible.InPhysBoneChain": "当前对象由 PhysicsBone 控制,可能无法在第一人称视角中可见;请指定 PhysicsBone 链的起点。", "blendshape.mesh": "网格", @@ -227,7 +226,6 @@ "setup_outfit.err.no_avatar_descriptor": "在 {0} 的父级中找不到 VRC Avatar Descriptor。请确保您的服装放置在 Avatar 内。", "setup_outfit.err.no_animator": "您的 Avatar 没有动画控制器 (Animator) 组件。", "setup_outfit.err.no_hips": "您的 Avatar 没有 Hips 骨骼。Setup Outfit 只能用于 humanoid Avatars。", - "setup_outfit.err.no_outfit_hips": "无法识别服装的 Hips,已搜索包含以下名称的对象:", "move_independently.group-header": "要一起移动的对象", "scale_adjuster.scale": "调整比例", "scale_adjuster.adjust_children": "调整子级的位置", @@ -237,4 +235,4 @@ "ma_info.param_usage_ui.other_objects": "此 Avatar 上的其他对象", "ma_info.param_usage_ui.free_space": "未使用的参数空间 ({0} bits)", "ma_info.param_usage_ui.bits_template": "{0} ({1} bits)" -} \ No newline at end of file +} diff --git a/Editor/Localization/zh-Hant.json b/Editor/Localization/zh-Hant.json index 465eb62b..bdc8b921 100644 --- a/Editor/Localization/zh-Hant.json +++ b/Editor/Localization/zh-Hant.json @@ -99,7 +99,6 @@ "worldfixed.quest": "此元件未生效,因為它與 Android 環境不相容。", "worldfixed.normal": "當前物件將會固定於世界,除非你使用約束將它綁在 Avatar 內。", "fpvisible.normal": "當前物件將在第一人稱視角中可見。", - "fpvisible.NotUnderHead": "此元件未生效,因為它需要放置在 Head 骨骼下。", "fpvisible.quest": "此元件未生效,因為它與 Android 環境不相容。", "fpvisible.InPhysBoneChain": "當前物件由 Physics Bone 控制,可能無法在第一人稱視角中可見;請指定 Physics Bone 鏈的起點。", "blendshape.mesh": "網格", @@ -239,7 +238,6 @@ "setup_outfit.err.no_avatar_descriptor": "在 {0} 的父級中找不到 VRC Avatar Descriptor。請確保你的服裝放置在 Avatar 裡。", "setup_outfit.err.no_animator": "你的 Avatar 沒有 Animator 元件。", "setup_outfit.err.no_hips": "你的 Avatar 沒有 Hips 骨骼。Setup Outfit 只能用於 humanoid Avatars。", - "setup_outfit.err.no_outfit_hips": "識別不到服裝的 Hips 骨骼,已搜尋含有以下名稱的骨骼物件:", "move_independently.group-header": "要一起移動的物件", "scale_adjuster.scale": "調整比例", "scale_adjuster.adjust_children": "調整子級的位置", @@ -253,7 +251,7 @@ "reactive_object.inverse": "反轉條件", "reactive_object.material-setter.set-to": "將材質設定為:", "menuitem.misc.add_toggle": "新增開關", - "ro_sim.open_debugger_button": "開啟響應除錯工具", + "ro_sim.open_debugger_button": "開啟 Reaction 除錯工具", "ro_sim.window.title": "MA 響應除錯工具", "ro_sim.header.inspecting": "檢視物件", "ro_sim.header.clear_overrides": "清除所有覆寫", @@ -275,4 +273,4 @@ "ro_sim.effect_group.rule_inverted": "規則的條件已反轉", "ro_sim.effect_group.rule_inverted.tooltip": "這條規則將在未達成其任一條件時套用。", "ro_sim.effect_group.conditions": "條件" -} \ No newline at end of file +} diff --git a/Editor/Menu/VirtualMenu.cs b/Editor/Menu/VirtualMenu.cs index 24b5971f..c50ee654 100644 --- a/Editor/Menu/VirtualMenu.cs +++ b/Editor/Menu/VirtualMenu.cs @@ -7,6 +7,7 @@ using System.Linq; using JetBrains.Annotations; using nadena.dev.modular_avatar.core.menu; using nadena.dev.modular_avatar.editor.ErrorReporting; +using nadena.dev.ndmf; using UnityEngine; using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.ScriptableObjects; @@ -102,7 +103,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu PushControl(control); } - if (_menuToInstallerMap.TryGetValue(expMenu, out var installers)) + if (_menuToInstallerMap.TryGetValue(ObjectRegistry.GetReference(expMenu), out var installers)) { foreach (var installer in installers) { @@ -311,7 +312,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu // initial validation if (installer.menuToAppend == null && installer.GetComponent() == null) return; - var target = installer.installTargetMenu ? (object) installer.installTargetMenu : RootMenuKey; + var target = installer.installTargetMenu ? (object) ObjectRegistry.GetReference(installer.installTargetMenu) : RootMenuKey; if (!_targetMenuToInstaller.TryGetValue(target, out var targets)) { targets = new List(); @@ -366,7 +367,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu } // Some menu installers may be bound to the root menu _asset_ directly. - if (menuToInstallerFiltered.TryGetValue(menu, out var installers)) + if (menuToInstallerFiltered.TryGetValue(ObjectRegistry.GetReference(menu), out var installers)) { foreach (var installer in installers) { diff --git a/Editor/MergeAnimatorProcessor.cs b/Editor/MergeAnimatorProcessor.cs index 223cfde7..b0af81ac 100644 --- a/Editor/MergeAnimatorProcessor.cs +++ b/Editor/MergeAnimatorProcessor.cs @@ -236,14 +236,28 @@ namespace nadena.dev.modular_avatar.core.editor } } - private bool? ProbeWriteDefaults(AnimatorController controller) + internal static bool? ProbeWriteDefaults(AnimatorController controller) { + if (controller == null) return null; + bool hasWDOn = false; bool hasWDOff = false; var stateMachineQueue = new Queue(); foreach (var layer in controller.layers) { + // Special case: A layer with a single state, which contains a blend tree, is ignored for WD analysis. + // This is because WD ON blend trees have different behavior from most WD ON states, and can be safely + // used in a WD OFF animator. + + if (layer.stateMachine.states.Length == 1 + && layer.stateMachine.states[0].state.motion is BlendTree + && layer.stateMachine.stateMachines.Length == 0 + ) + { + continue; + } + stateMachineQueue.Enqueue(layer.stateMachine); } diff --git a/Editor/OptimizationPasses/ConstraintConverterPass.cs b/Editor/OptimizationPasses/ConstraintConverterPass.cs index 10c8dccc..2dfa9a7e 100644 --- a/Editor/OptimizationPasses/ConstraintConverterPass.cs +++ b/Editor/OptimizationPasses/ConstraintConverterPass.cs @@ -45,7 +45,7 @@ namespace nadena.dev.modular_avatar.core.editor { var converters = context.AvatarRootObject.GetComponentsInChildren(true) .Select(c => c.gameObject) - .ToHashSet(new ObjectIdentityComparer()); + .ToHashSet(); if (converters.Count == 0) return; var constraintGameObjects = context.AvatarRootObject.GetComponentsInChildren(true) diff --git a/Editor/OptimizationPasses/GCGameObjectsPass.cs b/Editor/OptimizationPasses/GCGameObjectsPass.cs index 2dae8023..0214494a 100644 --- a/Editor/OptimizationPasses/GCGameObjectsPass.cs +++ b/Editor/OptimizationPasses/GCGameObjectsPass.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using UnityEditor; using UnityEngine; - +using Object = UnityEngine.Object; #if MA_VRCSDK3_AVATARS using VRC.SDK3.Dynamics.PhysBone.Components; #endif @@ -123,7 +123,7 @@ namespace nadena.dev.modular_avatar.core.editor } } } - catch (MissingComponentException _) + catch (MissingComponentException) { // No animator? weird. Move on. } @@ -194,7 +194,7 @@ namespace nadena.dev.modular_avatar.core.editor { if (!referencedGameObjects.Contains(go)) { - UnityEngine.Object.DestroyImmediate(go); + Object.DestroyImmediate(go); } } } diff --git a/Editor/ParamsUsage/MAParametersIntrospection.cs b/Editor/ParamsUsage/MAParametersIntrospection.cs index 2388d178..1924c2d0 100644 --- a/Editor/ParamsUsage/MAParametersIntrospection.cs +++ b/Editor/ParamsUsage/MAParametersIntrospection.cs @@ -34,20 +34,12 @@ namespace nadena.dev.modular_avatar.core.editor hidden = true; } - var type = AnimatorControllerParameterType.Bool; - - if (type != AnimatorControllerParameterType.Float && - (_component.Control.value > 1.01 || _component.Control.value < -0.01)) - type = AnimatorControllerParameterType.Int; - - if (Mathf.Abs(Mathf.Round(_component.Control.value) - _component.Control.value) > 0.01f) - type = AnimatorControllerParameterType.Float; - yield return new ProvidedParameter( name, ParameterNamespace.Animator, - _component, PluginDefinition.Instance, type) + _component, PluginDefinition.Instance, _component.AnimatorControllerParameterType) { + ExpandTypeOnConflict = true, WantSynced = _component.isSynced, IsHidden = hidden, DefaultValue = _component.isDefault ? _component.Control.value : null diff --git a/Editor/ParamsUsage/ParamsUsageWindow.cs b/Editor/ParamsUsage/ParamsUsageWindow.cs index 4d8f58ee..a7943a77 100644 --- a/Editor/ParamsUsage/ParamsUsageWindow.cs +++ b/Editor/ParamsUsage/ParamsUsageWindow.cs @@ -109,7 +109,7 @@ namespace nadena.dev.modular_avatar.core.editor { return uxml != null && EditorStyles.label != null; } - catch (NullReferenceException _) + catch (NullReferenceException) { return false; } diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index 69f179d7..2d258b23 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -59,6 +59,13 @@ namespace nadena.dev.modular_avatar.core.editor.plugin { seq.Run("Shape Changer", ctx => new ReactiveObjectPass(ctx).Execute()) .PreviewingWith(new ShapeChangerPreview(), new ObjectSwitcherPreview(), new MaterialSetterPreview()); +#if MA_VRCSDK3_AVATARS + // TODO: We currently run this above MergeArmaturePlugin, because Merge Armature might destroy + // game objects which contain Menu Installers. It'd probably be better however to teach Merge Armature + // to retain those objects? maybe? + seq.Run(MenuInstallPluginPass.Instance); +#endif + seq.Run(MergeArmaturePluginPass.Instance); seq.Run(BoneProxyPluginPass.Instance); seq.Run(VisibleHeadAccessoryPluginPass.Instance); @@ -73,7 +80,6 @@ namespace nadena.dev.modular_avatar.core.editor.plugin seq.Run(ConstraintConverterPass.Instance); }); #if MA_VRCSDK3_AVATARS - seq.Run(MenuInstallPluginPass.Instance); seq.Run(PhysbonesBlockerPluginPass.Instance); seq.Run("Fixup Expressions Menu", ctx => { diff --git a/Editor/ReactiveObjects/AnimationGeneration/AnimatedProperty.cs b/Editor/ReactiveObjects/AnimationGeneration/AnimatedProperty.cs index c3bded7d..6a488a5a 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/AnimatedProperty.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/AnimatedProperty.cs @@ -1,14 +1,14 @@ -using System.Collections.Generic; -using UnityEngine; +using System; +using System.Collections.Generic; +using System.Linq; +using Object = UnityEngine.Object; namespace nadena.dev.modular_avatar.core.editor { internal class AnimatedProperty { public TargetProp TargetProp { get; } - public string ControlParam { get; set; } - public bool alwaysDeleted; public object currentState; // Objects which trigger deletion of this shape key. @@ -25,5 +25,30 @@ namespace nadena.dev.modular_avatar.core.editor TargetProp = key; this.currentState = currentState; } + + protected bool Equals(AnimatedProperty other) + { + return Equals(currentState, other.currentState) && actionGroups.SequenceEqual(other.actionGroups) && + TargetProp.Equals(other.TargetProp); + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((AnimatedProperty)obj); + } + + public override int GetHashCode() + { + var actionGroupHash = 0; + foreach (var ag in actionGroups) + { + actionGroupHash = HashCode.Combine(actionGroupHash, ag); + } + + return HashCode.Combine(currentState, actionGroupHash, TargetProp); + } } } \ No newline at end of file diff --git a/Editor/ReactiveObjects/AnimationGeneration/ControlCondition.cs b/Editor/ReactiveObjects/AnimationGeneration/ControlCondition.cs index 33368ea5..f61b33e0 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ControlCondition.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ControlCondition.cs @@ -1,11 +1,13 @@ -using UnityEngine; +using System; +using UnityEngine; +using Object = UnityEngine.Object; namespace nadena.dev.modular_avatar.core.editor { internal class ControlCondition { public string Parameter; - public UnityEngine.Object DebugReference; + public Object DebugReference; public string DebugName; public bool IsConstant; @@ -14,5 +16,31 @@ namespace nadena.dev.modular_avatar.core.editor public bool IsConstantActive => InitiallyActive && IsConstant; public GameObject ReferenceObject; + + protected bool Equals(ControlCondition other) + { + return Parameter == other.Parameter + && Equals(DebugReference, other.DebugReference) + && DebugName == other.DebugName + && IsConstant == other.IsConstant + && ParameterValueLo.Equals(other.ParameterValueLo) + && ParameterValueHi.Equals(other.ParameterValueHi) + && InitialValue.Equals(other.InitialValue) + && Equals(ReferenceObject, other.ReferenceObject); + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((ControlCondition)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Parameter, DebugReference, DebugName, IsConstant, ParameterValueLo, + ParameterValueHi, InitialValue, ReferenceObject); + } } } \ No newline at end of file diff --git a/Editor/ReactiveObjects/AnimationGeneration/PropCache.cs b/Editor/ReactiveObjects/AnimationGeneration/PropCache.cs index 63fc36ef..ec87a881 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/PropCache.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/PropCache.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using nadena.dev.ndmf.preview; -using UnityEngine; +using nadena.dev.ndmf.preview.trace; namespace nadena.dev.modular_avatar.core.editor { @@ -13,54 +13,118 @@ namespace nadena.dev.modular_avatar.core.editor public PropCache Owner; public Key Key; public Value Value; + public string DebugName; + public int Generation; } + private readonly string _debugName; private readonly Func _operator; private readonly Func _equalityComparer; private readonly Dictionary _cache = new(); - public PropCache(Func operatorFunc, Func equalityComparer = null) + private static int _generation = 0; + + public PropCache(string debugName, Func operatorFunc, + Func equalityComparer = null) { + _debugName = debugName; _operator = operatorFunc; _equalityComparer = equalityComparer; } private static void InvalidateEntry(CacheEntry entry) { - var newGenContext = new ComputeContext("PropCache for key " + entry.Key); + var newGenContext = new ComputeContext("PropCache/" + entry.DebugName + " key " + FormatKey(entry.Key) + " gen=" + _generation++); var newValue = entry.Owner._operator(newGenContext, entry.Key); - if (entry.Owner._equalityComparer != null && entry.Owner._equalityComparer(entry.Value, newValue)) + if (!entry.ObserverContext.IsInvalidated && entry.Owner._equalityComparer != null && entry.Owner._equalityComparer(entry.Value, newValue)) { + TraceBuffer.RecordTraceEvent( + "PropCache.InvalidateEntry", + (ev) => $"[PropCache/{ev.Arg0}] Value did not change, retaining result (new gen={ev.Arg1})", + entry.DebugName, entry.Generation + ); + entry.GenerateContext = newGenContext; entry.GenerateContext.InvokeOnInvalidate(entry, InvalidateEntry); return; } + var trace = TraceBuffer.RecordTraceEvent( + "PropCache.InvalidateEntry", + (ev) => $"[PropCache/{ev.Arg0}] Value changed, invalidating", + entry.DebugName + ); + entry.Owner._cache.Remove(entry.Key); - entry.ObserverContext.Invalidate(); + using (trace.Scope()) entry.ObserverContext.Invalidate(); } public Value Get(ComputeContext context, Key key) { + TraceEvent ev; + var formattedKey = FormatKey(key); if (!_cache.TryGetValue(key, out var entry) || entry.GenerateContext.IsInvalidated) { - var subContext = new ComputeContext("PropCache for key " + key); + var curGen = _generation++; + + var subContext = new ComputeContext("PropCache/" + _debugName + " key " + formattedKey + " gen=" + curGen); entry = new CacheEntry { GenerateContext = subContext, - ObserverContext = new ComputeContext("Observer for PropCache for key " + key), + ObserverContext = new ComputeContext("Observer for PropCache/" + _debugName + " for key " + + formattedKey + " gen=" + curGen), Owner = this, Key = key, - Value = _operator(subContext, key) + DebugName = _debugName, + Generation = curGen }; + + ev = TraceBuffer.RecordTraceEvent( + "PropCache.Get", + (ev) => + { + var entry_ = (CacheEntry)ev.Arg0; + return + $"[PropCache/{entry_.DebugName}] Cache miss for key {entry_.Key} gen={entry_.Generation} from context {ev.Arg1}"; + }, + entry, context + ); + _cache[key] = entry; - - subContext.InvokeOnInvalidate(entry, InvalidateEntry); + using (ev.Scope()) + { + entry.Value = _operator(subContext, key); + entry.GenerateContext.InvokeOnInvalidate(entry, InvalidateEntry); + } + } + else + { + ev = TraceBuffer.RecordTraceEvent( + "PropCache.Get", + (ev) => + { + var entry_ = (CacheEntry) ev.Arg0; + return $"[PropCache/{entry_.DebugName}] Cache hit for key {entry_.Key} gen={entry_.Generation} from context {ev.Arg1}"; + }, + entry, context + ); } entry.ObserverContext.Invalidates(context); return entry.Value; } + + private static string FormatKey(object obj) + { + if (obj is UnityEngine.Object unityObj) + { + return $"{unityObj.GetHashCode()}#{unityObj}"; + } + else + { + return "" + obj; + } + } } } \ No newline at end of file diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactionRule.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactionRule.cs index 68ae3154..497046ab 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactionRule.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactionRule.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; -using nadena.dev.modular_avatar.animation; using UnityEngine; +using Object = UnityEngine.Object; namespace nadena.dev.modular_avatar.core.editor { @@ -9,8 +10,8 @@ namespace nadena.dev.modular_avatar.core.editor { public ReactionRule(TargetProp key, float value) : this(key, (object)value) { } - - public ReactionRule(TargetProp key, UnityEngine.Object value) + + public ReactionRule(TargetProp key, Object value) : this(key, (object)value) { } private ReactionRule(TargetProp key, object value) @@ -31,13 +32,15 @@ namespace nadena.dev.modular_avatar.core.editor public bool InitiallyActive => ((ControllingConditions.Count == 0) || ControllingConditions.All(c => c.InitiallyActive)) ^ Inverted; - public bool IsDelete; public bool Inverted; - public bool IsConstant => ControllingConditions.Count == 0 || ControllingConditions.All(c => c.IsConstant); - public bool IsConstantOn => IsConstant && InitiallyActive; + public bool IsConstant => ControllingConditions.Count == 0 + || ControllingConditions.All(c => c.IsConstant) + || ControllingConditions.Any(c => c.IsConstant && !c.InitiallyActive); + public bool IsConstantActive => IsConstant && InitiallyActive ^ Inverted; + public override string ToString() { return $"AGK: {TargetProp}={Value}"; @@ -55,9 +58,36 @@ namespace nadena.dev.modular_avatar.core.editor } else return false; if (!ControllingConditions.SequenceEqual(other.ControllingConditions)) return false; - if (IsDelete || other.IsDelete) return false; return true; } + + protected bool Equals(ReactionRule other) + { + return TargetProp.Equals(other.TargetProp) + && Equals(Value, other.Value) + && Equals(ControllingObject, other.ControllingObject) + && ControllingConditions.SequenceEqual(other.ControllingConditions) + && Inverted == other.Inverted; + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((ReactionRule)obj); + } + + public override int GetHashCode() + { + var ccHash = 0; + foreach (var cc in ControllingConditions) + { + ccHash = HashCode.Combine(ccHash, cc); + } + + return HashCode.Combine(TargetProp, Value, ControllingObject, ccHash, Inverted); + } } } \ No newline at end of file diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs index 1aeb0f1b..179e4ce4 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.LocateReactions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using nadena.dev.ndmf.preview; using UnityEngine; @@ -38,6 +39,77 @@ namespace nadena.dev.modular_avatar.core.editor return param; } } + + private readonly Dictionary<(SkinnedMeshRenderer, string), HashSet<(SkinnedMeshRenderer, string)>> + _blendshapeSyncMappings = new(); + + private void LocateBlendshapeSyncs(GameObject root) + { + var components = _computeContext.GetComponentsInChildren(root, true); + + foreach (var bss in components) + { + var localMesh = _computeContext.GetComponent(bss.gameObject); + if (localMesh == null) continue; + + foreach (var entry in _computeContext.Observe(bss, bss_ => bss_.Bindings.ToImmutableList(), + Enumerable.SequenceEqual)) + { + var src = entry.ReferenceMesh.Get(bss); + if (src == null) continue; + + var srcMesh = _computeContext.GetComponent(src); + + var localBlendshape = entry.LocalBlendshape; + if (string.IsNullOrWhiteSpace(localBlendshape)) + { + localBlendshape = entry.Blendshape; + } + + var srcBinding = (srcMesh, entry.Blendshape); + var dstBinding = (localMesh, localBlendshape); + + if (!_blendshapeSyncMappings.TryGetValue(srcBinding, out var dstSet)) + { + dstSet = new HashSet<(SkinnedMeshRenderer, string)>(); + _blendshapeSyncMappings[srcBinding] = dstSet; + } + + dstSet.Add(dstBinding); + } + } + + // For recursive blendshape syncs, we need to precompute the full set of affected blendshapes. + foreach (var (src, dsts) in _blendshapeSyncMappings) + { + var visited = new HashSet<(SkinnedMeshRenderer, string)>(); + foreach (var item in Visit(src, visited).ToList()) + { + dsts.Add(item); + } + } + + IEnumerable<(SkinnedMeshRenderer, string)> Visit( + (SkinnedMeshRenderer, string) key, + HashSet<(SkinnedMeshRenderer, string)> visited + ) + { + if (!visited.Add(key)) yield break; + + if (_blendshapeSyncMappings.TryGetValue(key, out var children)) + { + foreach (var child in children) + { + foreach (var item in Visit(child, visited)) + { + yield return item; + } + } + } + + yield return key; + } + } private void BuildConditions(Component controllingComponent, ReactionRule rule) { @@ -82,7 +154,7 @@ namespace nadena.dev.modular_avatar.core.editor Parameter = GetActiveSelfProxy(cursor.gameObject), DebugName = cursor.gameObject.name, IsConstant = false, - InitialValue = cursor.gameObject.activeSelf ? 1.0f : 0.0f, + InitialValue = _computeContext.Observe(cursor.gameObject, go => go.activeSelf) ? 1.0f : 0.0f, ParameterValueLo = 0.5f, ParameterValueHi = float.PositiveInfinity, ReferenceObject = cursor.gameObject, @@ -124,50 +196,80 @@ namespace nadena.dev.modular_avatar.core.editor var key = new TargetProp { TargetObject = renderer, - PropertyName = "blendShape." + shape.ShapeName, + PropertyName = BlendshapePrefix + shape.ShapeName }; + var currentValue = renderer.GetBlendShapeWeight(shapeId); var value = shape.ChangeType == ShapeChangeType.Delete ? 100 : shape.Value; - if (!shapeKeys.TryGetValue(key, out var info)) + + RegisterAction(key, currentValue, value, changer); + + if (_blendshapeSyncMappings.TryGetValue((renderer, shape.ShapeName), out var bindings)) { - info = new AnimatedProperty(key, renderer.GetBlendShapeWeight(shapeId)); - shapeKeys[key] = info; + // Propagate the new value through any Blendshape Syncs we might have. + // Note that we don't propagate deletes; it's common to e.g. want to delete breasts from the + // base model while retaining outerwear that matches the breast size. + foreach (var binding in bindings) + { + var bindingKey = new TargetProp + { + TargetObject = binding.Item1, + PropertyName = BlendshapePrefix + binding.Item2 + }; + var bindingRenderer = binding.Item1; - // Add initial state - var agk = new ReactionRule(key, value); - agk.Value = renderer.GetBlendShapeWeight(shapeId); - info.actionGroups.Add(agk); + var bindingMesh = bindingRenderer.sharedMesh; + if (bindingMesh == null) continue; + + var bindingShapeIndex = bindingMesh.GetBlendShapeIndex(binding.Item2); + if (bindingShapeIndex < 0) continue; + + var bindingInitialState = bindingRenderer.GetBlendShapeWeight(bindingShapeIndex); + + RegisterAction(bindingKey, bindingInitialState, value, changer); + } } - - var action = ObjectRule(key, changer, value); - action.Inverted = _computeContext.Observe(changer, c => c.Inverted); - var isCurrentlyActive = changer.gameObject.activeInHierarchy; - - if (shape.ChangeType == ShapeChangeType.Delete) + + key = new TargetProp { - action.IsDelete = true; - - if (isCurrentlyActive) info.currentState = 100; + TargetObject = renderer, + PropertyName = DeletedShapePrefix + shape.ShapeName + }; - info.actionGroups.Add(action); // Never merge - - continue; - } - - if (changer.gameObject.activeInHierarchy) info.currentState = action.Value; - - if (info.actionGroups.Count == 0) - { - info.actionGroups.Add(action); - } - else if (!info.actionGroups[^1].TryMerge(action)) - { - info.actionGroups.Add(action); - } + value = shape.ChangeType == ShapeChangeType.Delete ? 1 : 0; + RegisterAction(key, 0, value, changer); } } return shapeKeys; + + void RegisterAction(TargetProp key, float currentValue, float value, ModularAvatarShapeChanger changer) + { + if (!shapeKeys.TryGetValue(key, out var info)) + { + info = new AnimatedProperty(key, currentValue); + shapeKeys[key] = info; + + // Add initial state + var agk = new ReactionRule(key, value); + agk.Value = currentValue; + info.actionGroups.Add(agk); + } + + var action = ObjectRule(key, changer, value); + action.Inverted = _computeContext.Observe(changer, c => c.Inverted); + + if (changer.gameObject.activeInHierarchy) info.currentState = action.Value; + + if (info.actionGroups.Count == 0) + { + info.actionGroups.Add(action); + } + else if (!info.actionGroups[^1].TryMerge(action)) + { + info.actionGroups.Add(action); + } + } } private void FindMaterialSetters(Dictionary objectGroups, GameObject root) @@ -228,7 +330,8 @@ namespace nadena.dev.modular_avatar.core.editor if (!objectGroups.TryGetValue(key, out var group)) { - group = new AnimatedProperty(key, target.activeSelf ? 1 : 0); + var active = _computeContext.Observe(target, t => t.activeSelf); + group = new AnimatedProperty(key, active ? 1 : 0); objectGroups[key] = group; } diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs index 096c3e94..923ac135 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectAnalyzer.cs @@ -18,6 +18,11 @@ namespace nadena.dev.modular_avatar.core.editor private readonly ndmf.BuildContext _context; private readonly AnimationServicesContext _asc; private Dictionary _simulationInitialStates; + + public const string BlendshapePrefix = "blendShape."; + public const string DeletedShapePrefix = "deletedShape."; + + public bool OptimizeShapes = true; public ImmutableDictionary ForcePropertyOverrides { get; set; } = ImmutableDictionary.Empty; @@ -58,7 +63,6 @@ namespace nadena.dev.modular_avatar.core.editor { public Dictionary Shapes; public Dictionary InitialStates; - public HashSet DeletedShapes; } private static PropCache _analysisCache; @@ -67,7 +71,7 @@ namespace nadena.dev.modular_avatar.core.editor { if (_analysisCache == null) { - _analysisCache = new PropCache((ctx, root) => + _analysisCache = new PropCache("ROAnalyzer", (ctx, root) => { var analysis = new ReactiveObjectAnalyzer(ctx); analysis.ForcePropertyOverrides = ctx.Observe(ROSimulator.PropertyOverrides, a=>a, (a,b) => false) @@ -86,7 +90,6 @@ namespace nadena.dev.modular_avatar.core.editor /// /// The avatar root /// A dictionary of target property to initial state (float or UnityEngine.Object) - /// A hashset of blendshape properties which are always deleted /// public AnalysisResult Analyze( GameObject root @@ -98,9 +101,10 @@ namespace nadena.dev.modular_avatar.core.editor { result.Shapes = new(); result.InitialStates = new(); - result.DeletedShapes = new(); return result; } + + LocateBlendshapeSyncs(root); Dictionary shapes = FindShapes(root); FindObjectToggles(shapes, root); @@ -109,7 +113,7 @@ namespace nadena.dev.modular_avatar.core.editor ApplyInitialStateOverrides(shapes); AnalyzeConstants(shapes); ResolveToggleInitialStates(shapes); - PreprocessShapes(shapes, out result.InitialStates, out result.DeletedShapes); + PreprocessShapes(shapes, out result.InitialStates); result.Shapes = shapes; return result; @@ -144,7 +148,7 @@ namespace nadena.dev.modular_avatar.core.editor HashSet toggledObjects = new(); if (asc == null) return; - + foreach (var targetProp in shapes.Keys) if (targetProp is { TargetObject: GameObject go, PropertyName: "m_IsActive" }) toggledObjects.Add(go); @@ -157,9 +161,7 @@ namespace nadena.dev.modular_avatar.core.editor if (condition.ReferenceObject != null && !toggledObjects.Contains(condition.ReferenceObject)) condition.IsConstant = asc.AnimationDatabase.ClipsForPath(asc.PathMappings.GetObjectIdentifier(condition.ReferenceObject)).IsEmpty; - var i = 0; // Remove redundant active conditions. - int retain = 0; actionGroup.ControllingConditions.RemoveAll(c => c.IsConstant && c.InitiallyActive); } @@ -167,7 +169,7 @@ namespace nadena.dev.modular_avatar.core.editor group.actionGroups.RemoveAll(agk => agk.IsConstant && !agk.InitiallyActive); // Remove all action groups up until the last one where we're always on - var lastAlwaysOnGroup = group.actionGroups.FindLastIndex(ag => ag.IsConstantOn); + var lastAlwaysOnGroup = group.actionGroups.FindLastIndex(ag => ag.IsConstantActive); if (lastAlwaysOnGroup > 0) group.actionGroups.RemoveRange(0, lastAlwaysOnGroup - 1); } @@ -266,39 +268,26 @@ namespace nadena.dev.modular_avatar.core.editor } /// - /// Determine initial state and deleted shapes for all properties + /// Determine initial state for all properties /// /// /// - /// - private void PreprocessShapes(Dictionary shapes, out Dictionary initialStates, out HashSet deletedShapes) + private void PreprocessShapes(Dictionary shapes, + out Dictionary initialStates) { // For each shapekey, determine 1) if we can just set an initial state and skip and 2) if we can delete the // corresponding mesh. If we can't, delete ops are merged into the main list of operations. initialStates = new Dictionary(); - deletedShapes = new HashSet(); - + foreach (var (key, info) in shapes.ToList()) { if (info.actionGroups.Count == 0) { // never active control; ignore it entirely - shapes.Remove(key); + if (OptimizeShapes) shapes.Remove(key); continue; } - - var deletions = info.actionGroups.Where(agk => agk.IsDelete).ToList(); - if (deletions.Any(d => d.ControllingConditions.All(c => c.IsConstantActive))) - { - // always deleted - shapes.Remove(key); - deletedShapes.Add(key); - continue; - } - - // Move deleted shapes to the end of the list, so they override all Set actions - info.actionGroups = info.actionGroups.Where(agk => !agk.IsDelete).Concat(deletions).ToList(); var initialState = info.actionGroups.Where(agk => agk.InitiallyActive) .Select(agk => agk.Value) @@ -310,7 +299,7 @@ namespace nadena.dev.modular_avatar.core.editor // If we're now constant-on, we can skip animation generation if (info.actionGroups[^1].IsConstant) { - shapes.Remove(key); + if (OptimizeShapes) shapes.Remove(key); } } } diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs index ed4fd727..882f96bf 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPass.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using nadena.dev.modular_avatar.animation; using UnityEditor; @@ -25,6 +24,7 @@ namespace nadena.dev.modular_avatar.core.editor private HashSet activeProps = new(); private AnimationClip _initialStateClip; + private bool _writeDefaults; public ReactiveObjectPass(ndmf.BuildContext context) { @@ -33,14 +33,19 @@ namespace nadena.dev.modular_avatar.core.editor internal void Execute() { + // Having a WD OFF layer after WD ON layers can break WD. We match the behavior of the existing states, + // and if mixed, use WD ON to maximize compatibility. + _writeDefaults = MergeAnimatorProcessor.ProbeWriteDefaults(FindFxController().animatorController as AnimatorController) ?? true; + var analysis = new ReactiveObjectAnalyzer(context).Analyze(context.AvatarRootObject); var shapes = analysis.Shapes; var initialStates = analysis.InitialStates; - var deletedShapes = analysis.DeletedShapes; GenerateActiveSelfProxies(shapes); + ProcessMeshDeletion(initialStates, shapes); + ProcessInitialStates(initialStates, shapes); ProcessInitialAnimatorVariables(shapes); @@ -48,8 +53,6 @@ namespace nadena.dev.modular_avatar.core.editor { ProcessShapeKey(groups); } - - ProcessMeshDeletion(deletedShapes); } private void GenerateActiveSelfProxies(Dictionary shapes) @@ -80,7 +83,7 @@ namespace nadena.dev.modular_avatar.core.editor initialValues[condition.Parameter] = condition.InitialValue; } } - } + } private void ProcessInitialStates(Dictionary initialStates, Dictionary shapes) @@ -220,30 +223,65 @@ namespace nadena.dev.modular_avatar.core.editor #region Mesh processing - private void ProcessMeshDeletion(HashSet deletedKeys) + private void ProcessMeshDeletion(Dictionary initialStates, + Dictionary shapes) { - ImmutableDictionary> renderers = deletedKeys - .GroupBy( - v => (SkinnedMeshRenderer) v.TargetObject - ).ToImmutableDictionary( - g => (SkinnedMeshRenderer) g.Key, - g => g.ToList() - ); + var renderers = initialStates + .Where(kvp => kvp.Key.PropertyName.StartsWith(ReactiveObjectAnalyzer.DeletedShapePrefix)) + .Where(kvp => kvp.Key.TargetObject is SkinnedMeshRenderer) + .Where(kvp => kvp.Value is float f && f > 0.5f) + // Filter any non-constant keys + .Where(kvp => + { + if (!shapes.ContainsKey(kvp.Key)) + { + // Constant value + return true; + } - foreach (var (renderer, infos) in renderers) + var lastGroup = shapes[kvp.Key].actionGroups.LastOrDefault(); + return lastGroup?.IsConstantActive == true && lastGroup.Value is float f && f > 0.5f; + }) + .GroupBy(kvp => kvp.Key.TargetObject as SkinnedMeshRenderer) + .Select(grouping => (grouping.Key, grouping.Select( + kvp => kvp.Key.PropertyName.Substring(ReactiveObjectAnalyzer.DeletedShapePrefix.Length) + ).ToList())) + .ToList(); + foreach (var (renderer, shapeNamesToDelete) in renderers) { if (renderer == null) continue; var mesh = renderer.sharedMesh; if (mesh == null) continue; - renderer.sharedMesh = RemoveBlendShapeFromMesh.RemoveBlendshapes( - mesh, - infos - .Select(i => mesh.GetBlendShapeIndex(i.PropertyName.Substring("blendShape.".Length))) - .Where(k => k >= 0) - .ToList() - ); + var shapesToDelete = shapeNamesToDelete + .Select(shape => mesh.GetBlendShapeIndex(shape)) + .Where(k => k >= 0) + .ToList(); + + renderer.sharedMesh = RemoveBlendShapeFromMesh.RemoveBlendshapes(mesh, shapesToDelete); + + foreach (var name in shapeNamesToDelete) + { + // Don't need to animate this anymore...! + shapes.Remove(new TargetProp + { + TargetObject = renderer, + PropertyName = ReactiveObjectAnalyzer.BlendshapePrefix + name + }); + + shapes.Remove(new TargetProp + { + TargetObject = renderer, + PropertyName = ReactiveObjectAnalyzer.DeletedShapePrefix + name + }); + + initialStates.Remove(new TargetProp + { + TargetObject = renderer, + PropertyName = ReactiveObjectAnalyzer.BlendshapePrefix + name + }); + } } } @@ -252,10 +290,6 @@ namespace nadena.dev.modular_avatar.core.editor private void ProcessShapeKey(AnimatedProperty info) { // TODO: prune non-animated keys - - // Check if this is non-animated and skip most processing if so - if (info.alwaysDeleted || info.actionGroups[^1].IsConstant) return; - var asm = GenerateStateMachine(info); ApplyController(asm, "MA Responsive: " + info.TargetProp.TargetObject.name); } @@ -277,7 +311,7 @@ namespace nadena.dev.modular_avatar.core.editor var initial = new AnimationClip(); var initialState = new AnimatorState(); initialState.motion = initial; - initialState.writeDefaultValues = false; + initialState.writeDefaultValues = _writeDefaults; initialState.name = ""; asm.defaultState = initialState; @@ -351,7 +385,7 @@ namespace nadena.dev.modular_avatar.core.editor state.name = group.ControllingConditions[0].DebugName.Replace(".", "_"); state.motion = clip; - state.writeDefaultValues = false; + state.writeDefaultValues = _writeDefaults; states.Add(new ChildAnimatorState { position = new Vector3(x, y), @@ -527,13 +561,13 @@ namespace nadena.dev.modular_avatar.core.editor private void ApplyController(AnimatorStateMachine asm, string layerName) { - var fx = context.AvatarDescriptor.baseAnimationLayers - .FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX); + var fx = FindFxController(); + if (fx.animatorController == null) { throw new InvalidOperationException("No FX layer found"); } - + if (!context.IsTemporaryAsset(fx.animatorController)) { throw new InvalidOperationException("FX layer is not a temporary asset"); @@ -569,5 +603,13 @@ namespace nadena.dev.modular_avatar.core.editor } ).ToArray(); } + + private VRCAvatarDescriptor.CustomAnimLayer FindFxController() + { + var fx = context.AvatarDescriptor.baseAnimationLayers + .FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX); + + return fx; + } } } diff --git a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPrepass.cs b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPrepass.cs index 37e0de57..82c5faa8 100644 --- a/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPrepass.cs +++ b/Editor/ReactiveObjects/AnimationGeneration/ReactiveObjectPrepass.cs @@ -14,11 +14,11 @@ namespace nadena.dev.modular_avatar.core.editor protected override void Execute(ndmf.BuildContext context) { - var hasShapeChanger = context.AvatarRootObject.GetComponentInChildren() != null; + var hasShapeChanger = context.AvatarRootObject.GetComponentInChildren(true) != null; var hasObjectSwitcher = - context.AvatarRootObject.GetComponentInChildren() != null; + context.AvatarRootObject.GetComponentInChildren(true) != null; var hasMaterialSetter = - context.AvatarRootObject.GetComponentInChildren() != null; + context.AvatarRootObject.GetComponentInChildren(true) != null; if (hasShapeChanger || hasObjectSwitcher || hasMaterialSetter) { var clip = new AnimationClip(); diff --git a/Editor/ReactiveObjects/MaterialSetterPreview.cs b/Editor/ReactiveObjects/MaterialSetterPreview.cs index 29f0b07d..a181429d 100644 --- a/Editor/ReactiveObjects/MaterialSetterPreview.cs +++ b/Editor/ReactiveObjects/MaterialSetterPreview.cs @@ -28,7 +28,7 @@ namespace nadena.dev.modular_avatar.core.editor private const string PREFIX = "m_Materials.Array.data["; private PropCache> _cache = new( - GetMaterialOverridesForRenderer, Enumerable.SequenceEqual + "GetMaterialOverridesForRenderer", GetMaterialOverridesForRenderer, Enumerable.SequenceEqual ); private static ImmutableList<(int, Material)> GetMaterialOverridesForRenderer(ComputeContext ctx, Renderer r) diff --git a/Editor/ReactiveObjects/ParameterAssignerPass.cs b/Editor/ReactiveObjects/ParameterAssignerPass.cs index 5bfa59f7..9b891f84 100644 --- a/Editor/ReactiveObjects/ParameterAssignerPass.cs +++ b/Editor/ReactiveObjects/ParameterAssignerPass.cs @@ -87,23 +87,24 @@ namespace nadena.dev.modular_avatar.core.editor foreach (var (paramName, list) in _mamiByParam) { // Assign automatic values first - float defaultValue; + int? defaultValue = null; if (declaredParams.TryGetValue(paramName, out var p)) { - defaultValue = p.defaultValue; + defaultValue = (int) p.defaultValue; } else { - defaultValue = list.FirstOrDefault(m => m.isDefault && !m.automaticValue)?.Control?.value ?? 0; + var floatDefault = list.FirstOrDefault(m => m.isDefault && !m.automaticValue)?.Control?.value; + if (floatDefault.HasValue) defaultValue = (int) floatDefault.Value; - if (list.Count == 1) - // If we have only a single entry, it's probably an on-off toggle, so we'll implicitly let 0 - // be the 'unselected' default value (if this is not default) - defaultValue = 0; + if (list.Count == 1 && list[0].isDefault && list[0].automaticValue) + // If we have only a single entry, it's probably an on-off toggle, so we'll implicitly let 1 + // be the 'selected' default value (if this is default and automatic value) + defaultValue = 1; } HashSet usedValues = new(); - usedValues.Add((int)defaultValue); + if (defaultValue.HasValue) usedValues.Add(defaultValue.Value); foreach (var item in list) { @@ -113,24 +114,32 @@ namespace nadena.dev.modular_avatar.core.editor } } + if (!defaultValue.HasValue) + { + for (int i = 0; i < 256; i++) + { + if (!usedValues.Contains(i)) + { + defaultValue = i; + usedValues.Add(i); + break; + } + } + } + var nextValue = 1; - var canBeBool = true; - var canBeInt = true; - var isSaved = true; - var isSynced = true; + var valueType = VRCExpressionParameters.ValueType.Bool; + var isSaved = false; + var isSynced = false; foreach (var mami in list) { if (mami.automaticValue) { - if (list.Count == 1) + if (mami.isDefault) { - mami.Control.value = 1; - } - else if (mami.isDefault) - { - mami.Control.value = defaultValue; + mami.Control.value = defaultValue.GetValueOrDefault(); } else { @@ -141,28 +150,24 @@ namespace nadena.dev.modular_avatar.core.editor } } - if (Mathf.Abs(mami.Control.value - Mathf.Round(mami.Control.value)) > 0.01f) - canBeInt = false; - else - canBeBool &= mami.Control.value is >= 0 and <= 1; + var newValueType = mami.ExpressionParametersValueType; + if (valueType == VRCExpressionParameters.ValueType.Bool || newValueType == VRCExpressionParameters.ValueType.Float) + { + valueType = newValueType; + } - isSaved &= mami.isSaved; - isSynced &= mami.isSynced; + isSaved |= mami.isSaved; + isSynced |= mami.isSynced; } if (!declaredParams.ContainsKey(paramName)) { - VRCExpressionParameters.ValueType newType; - if (canBeBool) newType = VRCExpressionParameters.ValueType.Bool; - else if (canBeInt) newType = VRCExpressionParameters.ValueType.Int; - else newType = VRCExpressionParameters.ValueType.Float; - var newParam = new VRCExpressionParameters.Parameter { name = paramName, - valueType = newType, + valueType = valueType, saved = isSaved, - defaultValue = defaultValue, + defaultValue = defaultValue.GetValueOrDefault(), networkSynced = isSynced }; newParameters[paramName] = newParam; @@ -225,8 +230,8 @@ namespace nadena.dev.modular_avatar.core.editor // we basically force-disable any conditions for nonselected menu items and force-enable any for default // menu items. InitialValue = mami.isDefault ? mami.Control.value : -999, - ParameterValueLo = mami.Control.value - 0.5f, - ParameterValueHi = mami.Control.value + 0.5f, + ParameterValueLo = mami.Control.value - 0.005f, + ParameterValueHi = mami.Control.value + 0.005f, DebugReference = mami, }; } diff --git a/Editor/ReactiveObjects/RemoveBlendShapeFromMesh.cs b/Editor/ReactiveObjects/RemoveBlendShapeFromMesh.cs index 25a268df..c1105a64 100644 --- a/Editor/ReactiveObjects/RemoveBlendShapeFromMesh.cs +++ b/Editor/ReactiveObjects/RemoveBlendShapeFromMesh.cs @@ -128,7 +128,7 @@ namespace nadena.dev.modular_avatar.core.editor n_nrm[i] = o_nrm[newToOrigVertIndex[i]]; n_tan[i] = o_tan[newToOrigVertIndex[i]]; } - catch (IndexOutOfRangeException e) + catch (IndexOutOfRangeException) { throw; } @@ -250,7 +250,6 @@ namespace nadena.dev.modular_avatar.core.editor { List n2o = new List(toRetainVertices.Length); List o2n = new List(toRetainVertices.Length); - int i = 0; for (int j = 0; j < toRetainVertices.Length; j++) { diff --git a/Editor/ReactiveObjects/ShapeChangerPreview.cs b/Editor/ReactiveObjects/ShapeChangerPreview.cs index e26e48aa..58c09131 100644 --- a/Editor/ReactiveObjects/ShapeChangerPreview.cs +++ b/Editor/ReactiveObjects/ShapeChangerPreview.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; -using nadena.dev.modular_avatar.core.editor.Simulator; using nadena.dev.ndmf.preview; using UnityEngine; using Object = UnityEngine.Object; @@ -60,8 +59,8 @@ namespace nadena.dev.modular_avatar.core.editor } } - private PropCache>> - _blendshapeCache = new(ShapesForAvatar); + private readonly PropCache>> + _blendshapeCache = new("ShapesForAvatar", ShapesForAvatar); private static ImmutableDictionary> ShapesForAvatar(ComputeContext context, GameObject avatarRoot) { @@ -73,9 +72,9 @@ namespace nadena.dev.modular_avatar.core.editor var analysis = ReactiveObjectAnalyzer.CachedAnalyze(context, avatarRoot); var shapes = analysis.Shapes; - ImmutableDictionary>.Builder rendererStates = - ImmutableDictionary.CreateBuilder>( - new ObjectIdentityComparer() + var rendererStates = + ImmutableDictionary.CreateBuilder>( + ); var avatarRootTransform = avatarRoot.transform; @@ -84,16 +83,29 @@ namespace nadena.dev.modular_avatar.core.editor var target = prop.TargetProp; if (target.TargetObject == null || target.TargetObject is not SkinnedMeshRenderer r) continue; if (!r.transform.IsChildOf(avatarRootTransform)) continue; - if (!target.PropertyName.StartsWith("blendShape.")) continue; + var isDelete = false; + string shapeName = null; + if (target.PropertyName.StartsWith(ReactiveObjectAnalyzer.DeletedShapePrefix)) + { + isDelete = true; + shapeName = target.PropertyName.Substring(ReactiveObjectAnalyzer.DeletedShapePrefix.Length); + } + else if (target.PropertyName.StartsWith(ReactiveObjectAnalyzer.BlendshapePrefix)) + { + shapeName = target.PropertyName.Substring(ReactiveObjectAnalyzer.BlendshapePrefix.Length); + } + else + { + continue; + } + var mesh = r.sharedMesh; if (mesh == null) continue; - var shapeName = target.PropertyName.Substring("blendShape.".Length); - if (!rendererStates.TryGetValue(r, out var states)) { - states = ImmutableList<(int, float)>.Empty; + states = ImmutableDictionary.Empty; rendererStates[r] = states; } @@ -102,16 +114,32 @@ namespace nadena.dev.modular_avatar.core.editor var activeRule = prop.actionGroups.LastOrDefault(rule => rule.InitiallyActive); if (activeRule == null || activeRule.Value is not float value) continue; + if (activeRule.ControllingObject == null) continue; // default value is being inherited - value = Math.Clamp(value, 0, 100); - - if (activeRule.IsDelete) value = -1; - - states = states.Add((index, value)); + if (isDelete) + { + if (value < 0.5f) continue; + value = -1; + } + else + { + if (states.ContainsKey(index)) + { + // Delete takes precedence over set in preview + continue; + } + + value = Math.Clamp(value, 0, 100); + } + + states = states.SetItem(index, value); rendererStates[r] = states; } - - return rendererStates.ToImmutableDictionary(); + + return rendererStates.ToImmutableDictionary( + kvp => kvp.Key, + kvp => kvp.Value.Select(shapePair => (shapePair.Key, shapePair.Value) + ).ToImmutableList()); } private IEnumerable ShapesToGroups(GameObject avatarRoot, ImmutableDictionary> shapes) @@ -133,12 +161,18 @@ namespace nadena.dev.modular_avatar.core.editor .ToImmutableList(); } - public Task Instantiate(RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context) + public async Task Instantiate(RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context) { var shapeValues = group.GetData(); var node = new Node(shapeValues, proxyPairs.First().Item2 as SkinnedMeshRenderer, _blendshapeCache); - return node.Refresh(proxyPairs, context, 0); + var rv = await node.Refresh(proxyPairs, context, 0); + if (rv == null) + { + context.Invalidate(); + } + + return node; } private class Node : IRenderFilterNode diff --git a/Editor/ReactiveObjects/Simulator/ROSimulator.cs b/Editor/ReactiveObjects/Simulator/ROSimulator.cs index d4188fbc..40b9be8c 100644 --- a/Editor/ReactiveObjects/Simulator/ROSimulator.cs +++ b/Editor/ReactiveObjects/Simulator/ROSimulator.cs @@ -5,6 +5,7 @@ using System.Linq; using nadena.dev.modular_avatar.ui; using nadena.dev.ndmf.localization; using nadena.dev.ndmf.preview; +using nadena.dev.ndmf.preview.trace; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; @@ -14,8 +15,8 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator { internal class ROSimulator : EditorWindow, IHasCustomMenu { - public static PublishedValue> PropertyOverrides = new(null); - public static PublishedValue> MenuItemOverrides = new(null); + public static PublishedValue> PropertyOverrides = new(null, debugName: "ROSimulator.PropertyOverrides"); + public static PublishedValue> MenuItemOverrides = new(null, debugName: "ROSimulator.MenuItemOverrides"); internal static string ROOT_PATH = "Packages/nadena.dev.modular-avatar/Editor/ReactiveObjects/Simulator/"; private static string USS = ROOT_PATH + "ROSimulator.uss"; @@ -64,27 +65,41 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator private void OnEnable() { - PropertyOverrides.Value = ImmutableDictionary.Empty; - MenuItemOverrides.Value = ImmutableDictionary.Empty; - EditorApplication.delayCall += LoadUI; - Selection.selectionChanged += SelectionChanged; - is_enabled = true; + EditorApplication.delayCall += () => + { + PropertyOverrides.Value = ImmutableDictionary.Empty; + MenuItemOverrides.Value = ImmutableDictionary.Empty; + EditorApplication.delayCall += LoadUI; + EditorApplication.update += PeriodicRefresh; + Selection.selectionChanged += SelectionChanged; + is_enabled = true; + }; } private void OnDisable() { - Selection.selectionChanged -= SelectionChanged; is_enabled = false; // Delay this to ensure that we don't try to change this value from within assembly reload callbacks // (which generates a noisy exception) EditorApplication.delayCall += () => { + Selection.selectionChanged -= SelectionChanged; + EditorApplication.update -= PeriodicRefresh; + PropertyOverrides.Value = null; MenuItemOverrides.Value = null; }; } + private void PeriodicRefresh() + { + if (_refreshPending) + { + RefreshUI(); + } + } + private ComputeContext _lastComputeContext; private GameObject currentSelection; private GUIStyle lockButtonStyle; @@ -101,11 +116,21 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator _refreshPending = true; - EditorApplication.delayCall += RefreshUI; + // For some reason, this seems to get dropped occasionally, resulting in us being wedged with _refreshPending = true. + // Instead, we'll trigger this from EditorApplication.update... + // EditorApplication.delayCall += RefreshUI; } private void UpdatePropertyOverride(string prop, bool? enable, float f_val) { + var trace = TraceBuffer.RecordTraceEvent( + "ROSimulator.UpdatePropertyOverride", + (ev) => $"Property {ev.Arg0} set to {ev.Arg1}", + arg0: prop, + arg1: enable == null ? "null" : enable.Value ? f_val : 0f + ); + + using (trace.Scope()) if (enable == null) { PropertyOverrides.Value = PropertyOverrides.Value.Remove(prop); @@ -123,6 +148,15 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator private void UpdateMenuItemOverride(string prop, ModularAvatarMenuItem item, bool? value) { + var trace = TraceBuffer.RecordTraceEvent( + "ROSimulator.UpdateMenuItemOverride", + (ev) => $"MenuItem {ev.Arg0} for prop {ev.Arg1} set to {ev.Arg2}", + arg0: item.gameObject.name, + arg1: prop, + arg2: value == null ? "null" : value.Value ? "true" : "false" + ); + + using (trace.Scope()) if (value == null) { MenuItemOverrides.Value = MenuItemOverrides.Value.Remove(prop); @@ -222,7 +256,7 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator return; } - _btn_clear.SetEnabled(!PropertyOverrides.Value.IsEmpty || !MenuItemOverrides.Value.IsEmpty); + _btn_clear.SetEnabled(PropertyOverrides.Value?.IsEmpty == false || MenuItemOverrides.Value?.IsEmpty == false); e_debugInfo.style.display = DisplayStyle.Flex; @@ -230,6 +264,7 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator _lastComputeContext.InvokeOnInvalidate(this, MaybeRefreshUI); var analysis = new ReactiveObjectAnalyzer(_lastComputeContext); + analysis.OptimizeShapes = false; analysis.ForcePropertyOverrides = PropertyOverrides.Value; analysis.ForceMenuItems = MenuItemOverrides.Value; var result = analysis.Analyze(avatar.gameObject); @@ -286,7 +321,7 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator // these properties in a closure _menuItemOverrideProperty = prop; _menuItemOverrideTarget = mami; - soc.OnStateOverrideChanged += MenuItemOverrideChanged; + soc.OnStateOverrideChanged = MenuItemOverrideChanged; } private void MenuItemOverrideChanged(bool? obj) @@ -319,7 +354,7 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator _propertyOverrideProperty = property; _propertyOverrideTargetValue = targetValue; - soc.OnStateOverrideChanged += OnParameterOverrideChanged; + soc.OnStateOverrideChanged = OnParameterOverrideChanged; } private void OnParameterOverrideChanged(bool? state) @@ -437,7 +472,7 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator var f_set_inactive = effectGroup.Q("effect__set-inactive"); var f_value = effectGroup.Q("effect__value"); var f_material = effectGroup.Q("effect__material"); - var f_delete = effectGroup.Q("effect__deleted"); + var f_delete = effectGroup.Q("effect__deleted"); f_target_component.style.display = DisplayStyle.None; f_target_component.SetEnabled(false); @@ -470,9 +505,10 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator f_property.value = targetProp.PropertyName; f_property.style.display = DisplayStyle.Flex; - if (reactionRule.IsDelete) + if (reactionRule.TargetProp.PropertyName.StartsWith(ReactiveObjectAnalyzer.DeletedShapePrefix)) { f_delete.style.display = DisplayStyle.Flex; + f_delete.value = reactionRule.Value is > 0.5f ? "DELETE" : "RETAIN"; } else if (reactionRule.Value is float f) { f_value.SetValueWithoutNotify(f); @@ -539,11 +575,11 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator soc.SetWithoutNotify(menuOverride); } - soc.OnStateOverrideChanged += value => { UpdateMenuItemOverride(prop, mami, value); }; + soc.OnStateOverrideChanged = value => { UpdateMenuItemOverride(prop, mami, value); }; } else { - soc.OnStateOverrideChanged += value => UpdatePropertyOverride(prop, value, targetValue); + soc.OnStateOverrideChanged = value => UpdatePropertyOverride(prop, value, targetValue); } var active = condition.InitiallyActive; diff --git a/Editor/ReactiveObjects/Simulator/StateOverrideController.cs b/Editor/ReactiveObjects/Simulator/StateOverrideController.cs index 1e05122f..74d9c45e 100644 --- a/Editor/ReactiveObjects/Simulator/StateOverrideController.cs +++ b/Editor/ReactiveObjects/Simulator/StateOverrideController.cs @@ -16,7 +16,7 @@ namespace nadena.dev.modular_avatar.core.editor private static StyleSheet uss; private Button btn_disable, btn_default, btn_enable; - public event System.Action OnStateOverrideChanged; + public System.Action OnStateOverrideChanged; public StateOverrideController() { diff --git a/Editor/RenameParametersHook.cs b/Editor/RenameParametersHook.cs index a47e27cc..5bd401c3 100644 --- a/Editor/RenameParametersHook.cs +++ b/Editor/RenameParametersHook.cs @@ -116,6 +116,7 @@ namespace nadena.dev.modular_avatar.core.editor } ResolvedParameter.saved |= info.ResolvedParameter.saved; + ResolvedParameter.localOnly &= info.ResolvedParameter.localOnly; } public void MergeChild(ParameterInfo info) @@ -305,7 +306,7 @@ namespace nadena.dev.modular_avatar.core.editor newParameter.defaultValue = info.ResolvedParameter.HasDefaultValue ? info.ResolvedParameter.defaultValue : parameter.defaultValue; newParameter.name = parameter.name; newParameter.valueType = parameter.valueType; - newParameter.networkSynced = parameter.networkSynced; + newParameter.networkSynced = parameter.networkSynced || !info.ResolvedParameter.localOnly; newParameter.saved = parameter.saved || info.ResolvedParameter.saved; return newParameter; diff --git a/Editor/ScaleAdjuster/ScaleAdjustedBones.cs b/Editor/ScaleAdjuster/ScaleAdjustedBones.cs index fdda046c..1220e982 100644 --- a/Editor/ScaleAdjuster/ScaleAdjustedBones.cs +++ b/Editor/ScaleAdjuster/ScaleAdjustedBones.cs @@ -31,7 +31,7 @@ namespace nadena.dev.modular_avatar.core.editor.ScaleAdjuster public BoneState parentHint; } - private Dictionary _bones = new(new ObjectIdentityComparer()); + private readonly Dictionary _bones = new(); //private List _states = new List(); public void Clear() diff --git a/Editor/ScaleAdjuster/ScaleAdjusterPreview.cs b/Editor/ScaleAdjuster/ScaleAdjusterPreview.cs index 31f3beb6..3b34cec1 100644 --- a/Editor/ScaleAdjuster/ScaleAdjusterPreview.cs +++ b/Editor/ScaleAdjuster/ScaleAdjusterPreview.cs @@ -59,29 +59,29 @@ namespace nadena.dev.modular_avatar.core.editor var scaleAdjusters = ctx.GetComponentsByType(); var avatarToRenderer = - new Dictionary>(new ObjectIdentityComparer()); + new Dictionary>(); - foreach (var adjuster in scaleAdjusters) + foreach (var root in ctx.GetAvatarRoots()) { - if (adjuster == null) continue; - - // Find parent object - // TODO: Reactive helper - var root = FindAvatarRootObserving(ctx, adjuster.gameObject); - if (root == null) continue; - - if (!avatarToRenderer.TryGetValue(root, out var renderers)) + if (ctx.GetComponentsInChildren(root, true).Length == 0) { - renderers = new HashSet(new ObjectIdentityComparer()); - avatarToRenderer.Add(root, renderers); + continue; + } - foreach (var renderer in root.GetComponentsInChildren()) - { - // For now, the preview system only supports MeshRenderer and SkinnedMeshRenderer - if (renderer is not MeshRenderer and not SkinnedMeshRenderer) continue; + if (ctx.GetAvatarRoot(root?.transform?.parent?.gameObject) != null) + { + continue; // nested avatar descriptor + } - renderers.Add(renderer); - } + var renderers = new HashSet(); + avatarToRenderer.Add(root, renderers); + + foreach (var renderer in root.GetComponentsInChildren()) + { + // For now, the preview system only supports MeshRenderer and SkinnedMeshRenderer + if (renderer is not MeshRenderer and not SkinnedMeshRenderer) continue; + + renderers.Add(renderer); } } @@ -97,6 +97,8 @@ namespace nadena.dev.modular_avatar.core.editor internal class ScaleAdjusterPreviewNode : IRenderFilterNode { + private readonly HashSet _knownProxies = new(); + private readonly GameObject SourceAvatarRoot; private readonly GameObject VirtualAvatarRoot; @@ -110,12 +112,12 @@ namespace nadena.dev.modular_avatar.core.editor private readonly Dictionary _shadowBoneMap; // Map from bones found in initial proxy state to shadow bones (with scale adjuster bones substituted) - private readonly Dictionary _finalBonesMap = new(new ObjectIdentityComparer()); + private readonly Dictionary _finalBonesMap = new(); private readonly Dictionary _scaleAdjusters = - new(new ObjectIdentityComparer()); + new(); - private Dictionary _rendererBoneStates = new(new ObjectIdentityComparer()); + private Dictionary _rendererBoneStates = new(); public ScaleAdjusterPreviewNode(ComputeContext context, RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs) @@ -129,28 +131,37 @@ namespace nadena.dev.modular_avatar.core.editor var priorScene = SceneManager.GetActiveScene(); var bonesSet = GetSourceBonesSet(context, proxyPairList); - var bones = bonesSet.ToArray(); + var bones = bonesSet.OrderBy(k => k.gameObject.name).ToArray(); + Transform[] sourceBones; Transform[] destinationBones; try { SceneManager.SetActiveScene(scene); VirtualAvatarRoot = new GameObject(avatarRoot.name + " [ScaleAdjuster]"); - _srcBones = new TransformAccessArray(bones.ToArray()); - destinationBones = CreateShadowBones(bones); + + _shadowBoneMap = CreateShadowBones(bones); + sourceBones = new Transform[_shadowBoneMap.Count]; + destinationBones = new Transform[_shadowBoneMap.Count]; + + var i = 0; + foreach (var (src, dst) in _shadowBoneMap) + { + sourceBones[i] = src; + destinationBones[i] = dst; + i++; + } } finally { SceneManager.SetActiveScene(priorScene); } - _shadowBoneMap = new Dictionary(new ObjectIdentityComparer()); - for (var i = 0; i < bones.Length; i++) _shadowBoneMap[bones[i]] = destinationBones[i]; - + _srcBones = new TransformAccessArray(sourceBones); _dstBones = new TransformAccessArray(destinationBones); - _boneIsValid = new NativeArray(bones.Length, Allocator.Persistent); - _boneStates = new NativeArray(bones.Length, Allocator.Persistent); + _boneIsValid = new NativeArray(sourceBones.Length, Allocator.Persistent); + _boneStates = new NativeArray(sourceBones.Length, Allocator.Persistent); FindScaleAdjusters(context); TransferBoneStates(); @@ -158,7 +169,7 @@ namespace nadena.dev.modular_avatar.core.editor private HashSet GetSourceBonesSet(ComputeContext context, List<(Renderer, Renderer)> proxyPairs) { - var bonesSet = new HashSet(new ObjectIdentityComparer()); + var bonesSet = new HashSet(); foreach (var (_, r) in proxyPairs) { if (r == null) continue; @@ -170,8 +181,12 @@ namespace nadena.dev.modular_avatar.core.editor if (smr == null) continue; foreach (var b in context.Observe(smr, smr_ => smr_.bones, Enumerable.SequenceEqual)) + { if (b != null) + { bonesSet.Add(b); + } + } } return bonesSet; @@ -182,10 +197,14 @@ namespace nadena.dev.modular_avatar.core.editor _finalBonesMap.Clear(); foreach (var (sa, proxy) in _scaleAdjusters.ToList()) + { // Note: We leak the proxy here, as destroying it can cause visual artifacts. They'll eventually get // cleaned up whenever the pipeline is fully reset, or when the scene is reloaded. if (sa == null) + { _scaleAdjusters.Remove(sa); + } + } _scaleAdjusters.Clear(); @@ -213,6 +232,9 @@ namespace nadena.dev.modular_avatar.core.editor { if (SourceAvatarRoot == null) return Task.FromResult(null); + // Clean any destroyed objects out of _knownProxies to avoid growing this set indefinitely + _knownProxies.RemoveWhere(p => p == null); + var proxyPairList = proxyPairs.ToList(); if (!GetSourceBonesSet(context, proxyPairList).SetEquals(_shadowBoneMap.Keys)) @@ -224,14 +246,13 @@ namespace nadena.dev.modular_avatar.core.editor return Task.FromResult(this); } - private Transform[] CreateShadowBones(Transform[] srcBones) + private Dictionary CreateShadowBones(Transform[] srcBones) { - var srcToDst = new Dictionary(new ObjectIdentityComparer()); + var srcToDst = new Dictionary(); - var dstBones = new Transform[srcBones.Length]; - for (var i = 0; i < srcBones.Length; i++) dstBones[i] = GetShadowBone(srcBones[i]); + for (var i = 0; i < srcBones.Length; i++) GetShadowBone(srcBones[i]); - return dstBones; + return srcToDst; Transform GetShadowBone(Transform srcBone) { @@ -278,12 +299,14 @@ namespace nadena.dev.modular_avatar.core.editor BoneIsValid[index] = transform.isValid; if (transform.isValid) + { BoneStates[index] = new TransformState { localPosition = transform.position, localRotation = transform.rotation, localScale = transform.localScale }; + } } } @@ -321,7 +344,15 @@ namespace nadena.dev.modular_avatar.core.editor if (proxy == null) return; var curParent = proxy.transform.parent ?? original.transform.parent; - if (_finalBonesMap.TryGetValue(curParent, out var newRoot)) proxy.transform.SetParent(newRoot, false); + if (curParent != null && _finalBonesMap.TryGetValue(curParent, out var newRoot)) + { + // We need to remember this proxy so we can avoid destroying it when we destroy VirtualAvatarRoot + // in Dispose + + _knownProxies.Add(proxy.transform); + + proxy.transform.SetParent(newRoot, false); + } var smr = proxy as SkinnedMeshRenderer; if (smr == null) return; @@ -333,6 +364,14 @@ namespace nadena.dev.modular_avatar.core.editor public void Dispose() { + foreach (var proxy in _knownProxies) + { + if (proxy != null && proxy.IsChildOf(VirtualAvatarRoot.transform)) + { + proxy.transform.SetParent(null, false); + } + } + Object.DestroyImmediate(VirtualAvatarRoot); _srcBones.Dispose(); diff --git a/Editor/SetupOutfit.cs b/Editor/SetupOutfit.cs index 8d01774d..93b587fd 100644 --- a/Editor/SetupOutfit.cs +++ b/Editor/SetupOutfit.cs @@ -263,7 +263,7 @@ namespace nadena.dev.modular_avatar.core.editor return outfitHumanoidBones; } - private static void FixAPose(GameObject avatarRoot, Transform outfitArmature) + internal static void FixAPose(GameObject avatarRoot, Transform outfitArmature, bool strictMode = true) { var mergeArmature = outfitArmature.GetComponent(); if (mergeArmature == null) return; @@ -283,7 +283,7 @@ namespace nadena.dev.modular_avatar.core.editor { var lowerArm = (HumanBodyBones)((int)arm + 2); - // check if the rotation of the arm differs, but distances and origin point are the same + // check if the rotation of the arm differs(, but distances and origin point are the same when strictMode) var avatarArm = rootAnimator.GetBoneTransform(arm); var outfitArm = avatarToOutfit(avatarArm); @@ -293,22 +293,27 @@ namespace nadena.dev.modular_avatar.core.editor if (outfitArm == null) return; if (outfitLowerArm == null) return; - if ((avatarArm.position - outfitArm.position).magnitude > 0.001f) return; + if (strictMode) + { + if ((avatarArm.position - outfitArm.position).magnitude > 0.001f) return; - // check relative distance to lower arm as well - var avatarArmLength = (avatarLowerArm.position - avatarArm.position).magnitude; - var outfitArmLength = (outfitLowerArm.position - outfitArm.position).magnitude; + // check relative distance to lower arm as well + var avatarArmLength = (avatarLowerArm.position - avatarArm.position).magnitude; + var outfitArmLength = (outfitLowerArm.position - outfitArm.position).magnitude; - if (Mathf.Abs(avatarArmLength - outfitArmLength) > 0.001f) return; + if (Mathf.Abs(avatarArmLength - outfitArmLength) > 0.001f) return; + } else { + if (Vector3.Dot((outfitLowerArm.position - outfitArm.position).normalized, (avatarLowerArm.position - avatarArm.position).normalized) > 0.999f) return; + } - // Rotate the outfit arm to ensure these two points match. + // Rotate the outfit arm to ensure these two bone orientations match. + Undo.RecordObject(outfitArm, "Convert A/T Pose"); var relRot = Quaternion.FromToRotation( outfitLowerArm.position - outfitArm.position, avatarLowerArm.position - avatarArm.position ); outfitArm.rotation = relRot * outfitArm.rotation; PrefabUtility.RecordPrefabInstancePropertyModifications(outfitArm); - EditorUtility.SetDirty(outfitArm); } Transform avatarToOutfit(Transform avBone) @@ -419,7 +424,13 @@ namespace nadena.dev.modular_avatar.core.editor private static bool ValidateSetupOutfit(GameObject gameObj) { - Object obj; + if (gameObj == null) + { + errorHeader = S("setup_outfit.err.header.notarget"); + errorMessageGroups = new string[] { S("setup_outfit.err.no_selection") }; + return false; + } + errorHeader = S_f("setup_outfit.err.header", gameObj.name); var xform = gameObj.transform; diff --git a/README.md b/README.md index 033d49d3..c4b74d03 100644 --- a/README.md +++ b/README.md @@ -33,4 +33,4 @@ For more information, check out the [documentation](https://m-a.nadena.dev). * 部分的なアニメーターを親に統合することで、様々のギミックの実装を簡単にします。 * 他にもいろいろ! -詳しくは[ドキュメンテーションページにご参照ください](https://modular-avatar.nadena.dev/ja/). +詳しくは[ドキュメンテーションページをご覧ください](https://modular-avatar.nadena.dev/ja/). diff --git a/Runtime/ModularAvatarBlendshapeSync.cs b/Runtime/ModularAvatarBlendshapeSync.cs index cbaef34f..c6019341 100644 --- a/Runtime/ModularAvatarBlendshapeSync.cs +++ b/Runtime/ModularAvatarBlendshapeSync.cs @@ -72,7 +72,9 @@ namespace nadena.dev.modular_avatar.core private void Rebind() { + #if UNITY_EDITOR if (this == null) return; + if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this)) return; _editorBindings = new List(); @@ -110,6 +112,7 @@ namespace nadena.dev.modular_avatar.core } Update(); + #endif } private void Update() diff --git a/Runtime/ModularAvatarBoneProxy.cs b/Runtime/ModularAvatarBoneProxy.cs index 35f0cd7d..38957bdc 100644 --- a/Runtime/ModularAvatarBoneProxy.cs +++ b/Runtime/ModularAvatarBoneProxy.cs @@ -205,6 +205,13 @@ namespace nadena.dev.modular_avatar.core Transform iter = newTarget; + if (newTarget == null) + { + boneReference = HumanBodyBones.LastBone; + subPath = null; + return; + } + if (newTarget == avatarTransform) { boneReference = HumanBodyBones.LastBone; @@ -230,4 +237,4 @@ namespace nadena.dev.modular_avatar.core _targetCache = newTarget; } } -} \ No newline at end of file +} diff --git a/Runtime/ModularAvatarMenuItem.cs b/Runtime/ModularAvatarMenuItem.cs index e38023ee..b5fd5d31 100644 --- a/Runtime/ModularAvatarMenuItem.cs +++ b/Runtime/ModularAvatarMenuItem.cs @@ -149,6 +149,38 @@ namespace nadena.dev.modular_avatar.core if (control.subParameters.Length > maxSubParams) control.subParameters = control.subParameters.Take(maxSubParams).ToArray(); } + + internal VRCExpressionParameters.ValueType ExpressionParametersValueType + { + get + { + // 0, 1 + var type = VRCExpressionParameters.ValueType.Bool; + + // 2, 3, ..., (255) + if (Control.value > 1) + { + type = VRCExpressionParameters.ValueType.Int; + } + + // (-1.0), ..., -0.1, 0.1, ..., 0.9 + if (Control.value < 0 || Mathf.Abs(Control.value - Mathf.Round(Control.value)) > 0.01f) + { + type = VRCExpressionParameters.ValueType.Float; + } + + return type; + } + } + + internal AnimatorControllerParameterType AnimatorControllerParameterType + => ExpressionParametersValueType switch + { + VRCExpressionParameters.ValueType.Bool => AnimatorControllerParameterType.Bool, + VRCExpressionParameters.ValueType.Int => AnimatorControllerParameterType.Int, + VRCExpressionParameters.ValueType.Float => AnimatorControllerParameterType.Float, + _ => 0, + }; } } diff --git a/Runtime/ReactiveObjects/ModularAvatarMaterialSetter.cs b/Runtime/ReactiveObjects/ModularAvatarMaterialSetter.cs index 0a919dd0..dfe2cf7d 100644 --- a/Runtime/ReactiveObjects/ModularAvatarMaterialSetter.cs +++ b/Runtime/ReactiveObjects/ModularAvatarMaterialSetter.cs @@ -38,7 +38,7 @@ namespace nadena.dev.modular_avatar.core } [AddComponentMenu("Modular Avatar/MA Material Setter")] - [HelpURL("https://modular-avatar.nadena.dev/docs/reference/material-setter?lang=auto")] + [HelpURL("https://modular-avatar.nadena.dev/docs/reference/reaction/material-setter?lang=auto")] public class ModularAvatarMaterialSetter : ReactiveComponent, IHaveObjReferences { [SerializeField] private List m_objects = new(); diff --git a/Runtime/ReactiveObjects/ModularAvatarObjectToggle.cs b/Runtime/ReactiveObjects/ModularAvatarObjectToggle.cs index 93b1421a..74cc69a8 100644 --- a/Runtime/ReactiveObjects/ModularAvatarObjectToggle.cs +++ b/Runtime/ReactiveObjects/ModularAvatarObjectToggle.cs @@ -21,7 +21,7 @@ namespace nadena.dev.modular_avatar.core } [AddComponentMenu("Modular Avatar/MA Object Toggle")] - [HelpURL("https://modular-avatar.nadena.dev/docs/reference/object-toggle?lang=auto")] + [HelpURL("https://modular-avatar.nadena.dev/docs/reference/reaction/object-toggle?lang=auto")] public class ModularAvatarObjectToggle : ReactiveComponent, IHaveObjReferences { [SerializeField] private List m_objects = new(); diff --git a/Runtime/ReactiveObjects/ModularAvatarShapeChanger.cs b/Runtime/ReactiveObjects/ModularAvatarShapeChanger.cs index d8e1dea5..4fff480b 100644 --- a/Runtime/ReactiveObjects/ModularAvatarShapeChanger.cs +++ b/Runtime/ReactiveObjects/ModularAvatarShapeChanger.cs @@ -57,14 +57,9 @@ namespace nadena.dev.modular_avatar.core } [AddComponentMenu("Modular Avatar/MA Shape Changer")] - [HelpURL("https://modular-avatar.nadena.dev/docs/reference/shape-changer?lang=auto")] + [HelpURL("https://modular-avatar.nadena.dev/docs/reference/reaction/shape-changer?lang=auto")] public class ModularAvatarShapeChanger : ReactiveComponent, IHaveObjReferences { - // Migration field to help with 1.10-beta series avatar data. Since this was never in a released version of MA, - // this migration support will be removed in 1.10.0. - [SerializeField] [FormerlySerializedAs("targetRenderer")] [HideInInspector] - private AvatarObjectReference m_targetRenderer = new(); - [SerializeField] [FormerlySerializedAs("Shapes")] private List m_shapes = new(); @@ -82,40 +77,6 @@ namespace nadena.dev.modular_avatar.core } } - private void OnEnable() - { - MigrateTargetRenderer(); - } - - protected override void OnValidate() - { - base.OnValidate(); - MigrateTargetRenderer(); - } - - // Migrate early versions of MASC (from Modular Avatar 1.10.0-beta.4 or earlier) to the new format, where the - // target renderer is stored separately for each shape. - // This logic will be removed in 1.10.0. - private void MigrateTargetRenderer() - { - // Note: This method runs in the context of OnValidate, and therefore cannot touch any other unity objects. - if (!string.IsNullOrEmpty(m_targetRenderer.referencePath) || m_targetRenderer.targetObject != null) - { - foreach (var shape in m_shapes) - { - if (shape.Object == null) shape.Object = new AvatarObjectReference(); - - if (string.IsNullOrEmpty(shape.Object.referencePath) && shape.Object.targetObject == null) - { - shape.Object.referencePath = m_targetRenderer.referencePath; - shape.Object.targetObject = m_targetRenderer.targetObject; - } - } - m_targetRenderer.referencePath = null; - m_targetRenderer.targetObject = null; - } - } - public IEnumerable GetObjectReferences() { foreach (var shape in m_shapes) diff --git a/Runtime/Util/ObjectIdentityComparer.cs b/Runtime/Util/ObjectIdentityComparer.cs deleted file mode 100644 index 80d47032..00000000 --- a/Runtime/Util/ObjectIdentityComparer.cs +++ /dev/null @@ -1,23 +0,0 @@ -#region - -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -#endregion - -namespace nadena.dev.modular_avatar -{ - internal class ObjectIdentityComparer : IEqualityComparer - { - public bool Equals(T x, T y) - { - return (object)x == (object)y; - } - - public int GetHashCode(T obj) - { - if (obj == null) return 0; - return RuntimeHelpers.GetHashCode(obj); - } - } -} \ No newline at end of file diff --git a/Runtime/Util/ObjectIdentityComparer.cs.meta b/Runtime/Util/ObjectIdentityComparer.cs.meta deleted file mode 100644 index a1ec9a3e..00000000 --- a/Runtime/Util/ObjectIdentityComparer.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: e674cbd75db24fb2b238674cd7010edb -timeCreated: 1709448428 \ No newline at end of file diff --git a/UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs b/UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs index aec7e15a..897e323f 100644 --- a/UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs +++ b/UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using nadena.dev.ndmf; using NUnit.Framework; @@ -28,6 +29,11 @@ namespace modular_avatar_tests return transformMaskElements.SequenceEqual(other.transformMaskElements); } + public override int GetHashCode() + { + return HashCode.Combine(humanoidMaskElements, transformMaskElements); + } + public static ExtractedMask FromAvatarMask(AvatarMask mask) { var so = new SerializedObject(mask); diff --git a/UnitTests~/MergeAnimatorTests/ProxyAnim.meta b/UnitTests~/MergeAnimatorTests/ProxyAnim.meta new file mode 100644 index 00000000..659236e2 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/ProxyAnim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 130af01284a51c24f99eeb52361a81fb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/MergeAnimatorTests/ProxyAnim/New Animator Controller.controller b/UnitTests~/MergeAnimatorTests/ProxyAnim/New Animator Controller.controller new file mode 100644 index 00000000..9a6333ca --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/ProxyAnim/New Animator Controller.controller @@ -0,0 +1,72 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-9046052599989551153 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: proxy_tpose + 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: 645a7092829eff9478fb3a29f959a6fa, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: New Animator Controller + serializedVersion: 5 + m_AnimatorParameters: [] + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: proxy + m_StateMachine: {fileID: 1953483892909110087} + 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} +--- !u!1107 &1953483892909110087 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: proxy + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -9046052599989551153} + m_Position: {x: 360, y: 110, 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: -9046052599989551153} diff --git a/UnitTests~/MergeAnimatorTests/ProxyAnim/New Animator Controller.controller.meta b/UnitTests~/MergeAnimatorTests/ProxyAnim/New Animator Controller.controller.meta new file mode 100644 index 00000000..b2d87165 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/ProxyAnim/New Animator Controller.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 60603c8e68ac87447b02be4e3af6a7bd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.cs b/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.cs new file mode 100644 index 00000000..b67e30d1 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.cs @@ -0,0 +1,28 @@ +using modular_avatar_tests; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using NUnit.Framework; +using UnityEditor.Animations; +using UnityEngine; + +namespace UnitTests.MergeAnimatorTests.ProxyAnim +{ + public class ProxyAnimTest : TestBase + { + [Test] + public void whenProxyAnimIsMerged_itIsNotReplaced() + { + var root = CreatePrefab("ProxyAnimTest.prefab"); + + var originalAnimator = (AnimatorController) root.GetComponentInChildren().animator; + var originalClip = originalAnimator.layers[0].stateMachine.states[0].state.motion as AnimationClip; + + AvatarProcessor.ProcessAvatar(root); + + var resultLayer = findFxLayer(root, "proxy"); + var resultClip = resultLayer.stateMachine.states[0].state.motion as AnimationClip; + + Assert.AreEqual(originalClip, resultClip); + } + } +} \ No newline at end of file diff --git a/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.cs.meta b/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.cs.meta new file mode 100644 index 00000000..bc3638bd --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2cfba229fa1e4c718f5cb5dd579d3319 +timeCreated: 1728166108 \ No newline at end of file diff --git a/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.prefab b/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.prefab new file mode 100644 index 00000000..dff56d51 --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.prefab @@ -0,0 +1,378 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &603567390109878184 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2557688482630581002} + - component: {fileID: 2868037606074871127} + - component: {fileID: 1028276594299388724} + - component: {fileID: 223023489903813839} + m_Layer: 0 + m_Name: ProxyAnimTest + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2557688482630581002 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 603567390109878184} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.1618705, y: 1.0265146, z: 1.8807894} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 1 + m_Children: + - {fileID: 2189711873526373063} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &2868037606074871127 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 603567390109878184} + 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 &1028276594299388724 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 603567390109878184} + 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: 0 + 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: 0 + 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: 0} + mask: {fileID: 0} + isDefault: 1 + 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 &223023489903813839 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 603567390109878184} + 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 &1425973809379277617 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2189711873526373063} + - component: {fileID: 3380859768730913427} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2189711873526373063 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1425973809379277617} + 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: 1 + m_Children: [] + m_Father: {fileID: 2557688482630581002} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &3380859768730913427 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1425973809379277617} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3} + m_Name: + m_EditorClassIdentifier: + animator: {fileID: 9100000, guid: 60603c8e68ac87447b02be4e3af6a7bd, type: 2} + layerType: 5 + deleteAttachedAnimator: 1 + pathMode: 0 + matchAvatarWriteDefaults: 0 + relativePathRoot: + referencePath: + targetObject: {fileID: 0} + layerPriority: 0 diff --git a/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.prefab.meta b/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.prefab.meta new file mode 100644 index 00000000..6ceea0ce --- /dev/null +++ b/UnitTests~/MergeAnimatorTests/ProxyAnim/ProxyAnimTest.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a77dd3314cc88714bb6e9f1ad014cfc8 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/PropCacheTest.meta b/UnitTests~/PropCacheTest.meta new file mode 100644 index 00000000..158892ac --- /dev/null +++ b/UnitTests~/PropCacheTest.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 77aba4362cec4cea91ee64c6e640b6b2 +timeCreated: 1726436552 \ No newline at end of file diff --git a/UnitTests~/PropCacheTest/PropCacheTest.cs b/UnitTests~/PropCacheTest/PropCacheTest.cs new file mode 100644 index 00000000..2f0c9c5f --- /dev/null +++ b/UnitTests~/PropCacheTest/PropCacheTest.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using nadena.dev.modular_avatar.core.editor; +using nadena.dev.ndmf.preview; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace UnitTests.PropCacheTest +{ + public class PropCacheTest + { + [UnityTest] + public IEnumerator TestCacheInvalidation() + { + int seq = 0; + + Dictionary>> invalidators = new(); + PropCache cache = new PropCache("test", (ctx, k) => + { + Debug.Log("Generating value for " + k); + if (!invalidators.TryGetValue(k, out var list)) + { + list = new List>(); + invalidators[k] = list; + } + + list.Add(new WeakReference(ctx)); + + return (k * 10) + seq++; + }); + + ComputeContext ctx = new ComputeContext("c1"); + int val = cache.Get(ctx, 1); + Assert.AreEqual(10, val); + + ComputeContext ctx2 = new ComputeContext("c2"); + val = cache.Get(ctx2, 1); + Assert.AreEqual(10, val); + + invalidators[1][0].TryGetTarget(out var target); + target?.Invalidate(); + + Debug.Log("Pre-flush"); + ComputeContext.FlushInvalidates(); + Debug.Log("Mid-flush"); + ComputeContext.FlushInvalidates(); + Debug.Log("Post-flush"); + + // Task processing can happen asynchronously. + + int limit = 10; + while (limit-- > 0 && (!ctx.IsInvalidated || !ctx2.IsInvalidated)) + { + Debug.Log("Waiting for invalidation: " + limit); + Thread.Sleep(100); + } + + Assert.IsTrue(ctx.IsInvalidated); + Assert.IsTrue(ctx2.IsInvalidated); + + val = cache.Get(ctx, 1); + Assert.AreEqual(12, val); + + yield return null; + } + } +} \ No newline at end of file diff --git a/UnitTests~/PropCacheTest/PropCacheTest.cs.meta b/UnitTests~/PropCacheTest/PropCacheTest.cs.meta new file mode 100644 index 00000000..ef69296a --- /dev/null +++ b/UnitTests~/PropCacheTest/PropCacheTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6891e8d136c942878bca9b50b5c58ec9 +timeCreated: 1726436558 \ No newline at end of file diff --git a/UnitTests~/ReactiveComponent/BlendshapeSyncTest.cs b/UnitTests~/ReactiveComponent/BlendshapeSyncTest.cs new file mode 100644 index 00000000..19d61e6c --- /dev/null +++ b/UnitTests~/ReactiveComponent/BlendshapeSyncTest.cs @@ -0,0 +1,61 @@ +using System.Linq; +using modular_avatar_tests; +using nadena.dev.modular_avatar.core.editor; +using NUnit.Framework; +using UnityEngine; + +namespace UnitTests.ReactiveComponent +{ + public class BlendshapeSyncTest : TestBase + { + [Test] + public void blendshapeSync_propagatesThroughMeshes() + { + var root = CreatePrefab("BlendshapeSyncTest.prefab"); + + var analysis = new ReactiveObjectAnalyzer().Analyze(root); + + var m1 = analysis.Shapes[new TargetProp() + { + TargetObject = root.transform.Find("m1").GetComponent(), + PropertyName = "blendShape.bottom" + }]; + var m2 = analysis.Shapes[new TargetProp() + { + TargetObject = root.transform.Find("m2").GetComponent(), + PropertyName = "blendShape.bottom" + }]; + var m3 = analysis.Shapes[new TargetProp() + { + TargetObject = root.transform.Find("m3").GetComponent(), + PropertyName = "blendShape.top" + }]; + + Assert.IsTrue(analysis.Shapes.ContainsKey(new TargetProp() + { + TargetObject = root.transform.Find("m1").GetComponent(), + PropertyName = "deletedShape.bottom" + })); + + Assert.AreEqual(4, analysis.Shapes.Count); + + foreach (var ag in m1.actionGroups) + { + ag.TargetProp = new TargetProp(); + } + + foreach (var ag in m2.actionGroups) + { + ag.TargetProp = new TargetProp(); + } + + foreach (var ag in m3.actionGroups) + { + ag.TargetProp = new TargetProp(); + } + + Assert.AreEqual(m2.actionGroups, m1.actionGroups); + Assert.AreEqual(m3.actionGroups, m1.actionGroups); + } + } +} \ No newline at end of file diff --git a/UnitTests~/ReactiveComponent/BlendshapeSyncTest.cs.meta b/UnitTests~/ReactiveComponent/BlendshapeSyncTest.cs.meta new file mode 100644 index 00000000..c614946b --- /dev/null +++ b/UnitTests~/ReactiveComponent/BlendshapeSyncTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f74d4e816d0247159d977e21ebc57458 +timeCreated: 1728168066 \ No newline at end of file diff --git a/UnitTests~/ReactiveComponent/BlendshapeSyncTest.prefab b/UnitTests~/ReactiveComponent/BlendshapeSyncTest.prefab new file mode 100644 index 00000000..c0b98180 --- /dev/null +++ b/UnitTests~/ReactiveComponent/BlendshapeSyncTest.prefab @@ -0,0 +1,799 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &4750389987621451750 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3724328547737935111} + - component: {fileID: 6141552197888553193} + - component: {fileID: 866105862211182099} + - component: {fileID: 5955186656294850035} + m_Layer: 0 + m_Name: New Toggle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3724328547737935111 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4750389987621451750} + 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: 1 + m_Children: [] + m_Father: {fileID: 295226914695240947} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &6141552197888553193 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4750389987621451750} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3b29d45007c5493d926d2cd45a489529, type: 3} + m_Name: + m_EditorClassIdentifier: + Control: + name: New 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: 1 + automaticValue: 1 +--- !u!114 &866105862211182099 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4750389987621451750} + 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!114 &5955186656294850035 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4750389987621451750} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 0 + m_shapes: + - Object: + referencePath: m1 + targetObject: {fileID: 7767603723203631002} + ShapeName: bottom + ChangeType: 1 + Value: 100 +--- !u!1 &7303978391080220300 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 295226914695240947} + - component: {fileID: 1177795503533185300} + - component: {fileID: 7792711537747161192} + - component: {fileID: 8538029171187693289} + m_Layer: 0 + m_Name: BlendshapeSyncTest + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &295226914695240947 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7303978391080220300} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.1618705, y: 1.0265146, z: 1.8807894} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 1 + m_Children: + - {fileID: 6955742288500591904} + - {fileID: 2035853062413530075} + - {fileID: 2118711245520540949} + - {fileID: 3724328547737935111} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &1177795503533185300 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7303978391080220300} + 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 &7792711537747161192 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7303978391080220300} + 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: 0 + 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: 0 + 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: 0} + mask: {fileID: 0} + isDefault: 1 + 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 &8538029171187693289 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7303978391080220300} + 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!1001 &1940371780088492798 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 295226914695240947} + m_Modifications: + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.x + value: 5.33 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.w + value: 0.7071067 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.x + value: -0.7071068 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_ConstrainProportionsScale + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_DirtyAABB + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.z + value: 2 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_BlendShapeWeights.Array.data[0] + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 919132149155446097, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_Name + value: m3 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + insertIndex: -1 + addedObject: {fileID: 4462016549056391230} + m_SourcePrefab: {fileID: 100100000, guid: fe5b76dae94c07345b74d51e9a9a8440, type: 3} +--- !u!1 &1597929094539956143 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 919132149155446097, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + m_PrefabInstance: {fileID: 1940371780088492798} + m_PrefabAsset: {fileID: 0} +--- !u!114 &4462016549056391230 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1597929094539956143} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6fd7cab7d93b403280f2f9da978d8a4f, type: 3} + m_Name: + m_EditorClassIdentifier: + Bindings: + - ReferenceMesh: + referencePath: m2 + targetObject: {fileID: 1660453041666320737} + Blendshape: bottom + LocalBlendshape: top +--- !u!4 &2118711245520540949 stripped +Transform: + m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + m_PrefabInstance: {fileID: 1940371780088492798} + m_PrefabAsset: {fileID: 0} +--- !u!1001 &2002541556693849136 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 295226914695240947} + m_Modifications: + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.x + value: 2.75 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.w + value: 0.7071067 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.x + value: -0.7071068 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_ConstrainProportionsScale + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_DirtyAABB + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.z + value: 2 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_BlendShapeWeights.Array.data[0] + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 919132149155446097, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_Name + value: m2 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + insertIndex: -1 + addedObject: {fileID: 1532028097981150578} + m_SourcePrefab: {fileID: 100100000, guid: fe5b76dae94c07345b74d51e9a9a8440, type: 3} +--- !u!1 &1660453041666320737 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 919132149155446097, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + m_PrefabInstance: {fileID: 2002541556693849136} + m_PrefabAsset: {fileID: 0} +--- !u!114 &1532028097981150578 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1660453041666320737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6fd7cab7d93b403280f2f9da978d8a4f, type: 3} + m_Name: + m_EditorClassIdentifier: + Bindings: + - ReferenceMesh: + referencePath: m1 + targetObject: {fileID: 7767603723203631002} + Blendshape: bottom + LocalBlendshape: +--- !u!4 &2035853062413530075 stripped +Transform: + m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + m_PrefabInstance: {fileID: 2002541556693849136} + m_PrefabAsset: {fileID: 0} +--- !u!1001 &7425727422508624587 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 295226914695240947} + m_Modifications: + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.w + value: 0.7071067 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.x + value: -0.7071068 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_DirtyAABB + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.z + value: 2 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_Materials.Array.data[0] + value: + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_BlendShapeWeights.Array.data[0] + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 919132149155446097, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_Name + value: m1 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: fe5b76dae94c07345b74d51e9a9a8440, type: 3} +--- !u!4 &6955742288500591904 stripped +Transform: + m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + m_PrefabInstance: {fileID: 7425727422508624587} + m_PrefabAsset: {fileID: 0} +--- !u!1 &7767603723203631002 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 919132149155446097, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + m_PrefabInstance: {fileID: 7425727422508624587} + m_PrefabAsset: {fileID: 0} diff --git a/UnitTests~/ReactiveComponent/BlendshapeSyncTest.prefab.meta b/UnitTests~/ReactiveComponent/BlendshapeSyncTest.prefab.meta new file mode 100644 index 00000000..d6863646 --- /dev/null +++ b/UnitTests~/ReactiveComponent/BlendshapeSyncTest.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 05653f5cab04e764b80709fe866c1b35 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/ReactiveComponent/DeletionTest.prefab b/UnitTests~/ReactiveComponent/DeletionTest.prefab new file mode 100644 index 00000000..6cefc636 --- /dev/null +++ b/UnitTests~/ReactiveComponent/DeletionTest.prefab @@ -0,0 +1,666 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &2464504760772767737 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3251791125987375227} + - component: {fileID: 6611954401356246169} + - component: {fileID: 4257580493320060063} + - component: {fileID: 7095484051158404692} + m_Layer: 0 + m_Name: DeletionTest + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3251791125987375227 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2464504760772767737} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.06867766, y: 0.7869835, z: -0.57959247} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 1 + m_Children: + - {fileID: 8671858138418525756} + - {fileID: 3787319563290092876} + - {fileID: 2780879708549973278} + - {fileID: 6867583134219554799} + - {fileID: 3617623734196600728} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &6611954401356246169 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2464504760772767737} + 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 &4257580493320060063 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2464504760772767737} + 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: 0 + 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: 0 + 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: 0} + mask: {fileID: 0} + isDefault: 1 + 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 &7095484051158404692 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2464504760772767737} + 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 &3134446681435896768 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2780879708549973278} + - component: {fileID: 2470606632396626262} + m_Layer: 0 + m_Name: Delete + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2780879708549973278 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3134446681435896768} + 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: 1 + m_Children: [] + m_Father: {fileID: 3251791125987375227} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &2470606632396626262 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3134446681435896768} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 0 + m_shapes: + - Object: + referencePath: shape deletion test mesh + targetObject: {fileID: 0} + ShapeName: bottom + ChangeType: 0 + Value: 50 +--- !u!1 &7874409458034691206 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3787319563290092876} + - component: {fileID: 8462455628590652122} + m_Layer: 0 + m_Name: PriorSet + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3787319563290092876 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7874409458034691206} + 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: 1 + m_Children: [] + m_Father: {fileID: 3251791125987375227} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8462455628590652122 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7874409458034691206} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 0 + m_shapes: + - Object: + referencePath: shape deletion test mesh + targetObject: {fileID: 0} + ShapeName: bottom + ChangeType: 1 + Value: 50 +--- !u!1 &7956182162252432618 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3617623734196600728} + - component: {fileID: 4167915178638071617} + - component: {fileID: 3280847981733507148} + m_Layer: 0 + m_Name: MenuSet + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &3617623734196600728 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7956182162252432618} + 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: 1 + m_Children: [] + m_Father: {fileID: 3251791125987375227} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4167915178638071617 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7956182162252432618} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 0 + m_shapes: + - Object: + referencePath: shape deletion test mesh + targetObject: {fileID: 0} + ShapeName: bottom + ChangeType: 1 + Value: 0 +--- !u!114 &3280847981733507148 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7956182162252432618} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3b29d45007c5493d926d2cd45a489529, type: 3} + m_Name: + m_EditorClassIdentifier: + Control: + name: + 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 +--- !u!1 &8389945206789797712 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6867583134219554799} + - component: {fileID: 8099891503683627458} + m_Layer: 0 + m_Name: NullSet + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &6867583134219554799 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8389945206789797712} + 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: 1 + m_Children: [] + m_Father: {fileID: 3251791125987375227} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8099891503683627458 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8389945206789797712} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 0 + m_shapes: + - Object: + referencePath: shape deletion test mesh + targetObject: {fileID: 0} + ShapeName: bottom + ChangeType: 1 + Value: 0 +--- !u!1001 &9210451080691405271 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 3251791125987375227} + m_Modifications: + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.w + value: 0.7071067 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.x + value: -0.7071068 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_DirtyAABB + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_AABB.m_Extent.z + value: 2 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_BlendShapeWeights.Array.data[0] + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 919132149155446097, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + propertyPath: m_Name + value: shape deletion test mesh + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: fe5b76dae94c07345b74d51e9a9a8440, type: 3} +--- !u!4 &8671858138418525756 stripped +Transform: + m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: fe5b76dae94c07345b74d51e9a9a8440, + type: 3} + m_PrefabInstance: {fileID: 9210451080691405271} + m_PrefabAsset: {fileID: 0} diff --git a/UnitTests~/ReactiveComponent/DeletionTest.prefab.meta b/UnitTests~/ReactiveComponent/DeletionTest.prefab.meta new file mode 100644 index 00000000..0197de59 --- /dev/null +++ b/UnitTests~/ReactiveComponent/DeletionTest.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a82669288fc87d94db320a2494fd76c5 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/ReactiveComponent/ParameterAssignment/AutoValueAssignmentTests.cs b/UnitTests~/ReactiveComponent/ParameterAssignment/AutoValueAssignmentTests.cs index 62c3745c..d7eb71f5 100644 --- a/UnitTests~/ReactiveComponent/ParameterAssignment/AutoValueAssignmentTests.cs +++ b/UnitTests~/ReactiveComponent/ParameterAssignment/AutoValueAssignmentTests.cs @@ -16,10 +16,14 @@ namespace UnitTests.ReactiveComponent.ParameterAssignment [Test] public void ManuallyAssignedParametersAreNotReplaced() { - TestAssignments( - new[] { (false, 1.0f), (false, 4.0f) }, - new[] { 1.0f, 4.0f } - ); + TestAssignments(new[] { (false, 1.0f) }, new[] { 1.0f }); + TestAssignments(new[] { (false, 1.0f) }, new[] { 1.0f }, 0); + + TestAssignments(new[] { (false, 4.0f) }, new[] { 4.0f }); + TestAssignments(new[] { (false, 4.0f) }, new[] { 4.0f }, 0); + + TestAssignments(new[] { (false, 1.0f), (false, 4.0f) }, new[] { 1.0f, 4.0f }); + TestAssignments(new[] { (false, 1.0f), (false, 4.0f) }, new[] { 1.0f, 4.0f }, 0); } [Test] @@ -44,13 +48,18 @@ namespace UnitTests.ReactiveComponent.ParameterAssignment { TestAssignments(new[] { (false, 2.0f), (true, 0.0f), (true, 0.0f) }, new[] { 2.0f, 1.0f, 3.0f }, null); TestAssignments(new[] { (false, 2.7f), (true, 0.0f), (true, 0.0f) }, new[] { 2.7f, 1.0f, 3.0f }, null); + TestAssignments(new[] { (true, 1.0f), (false, 0.0f) }, new[] { 2.0f, 0.0f }, null, overrideExpectedDefaultValue: 1.0f); + TestAssignments(new[] { (true, 1.0f), (false, 0.0f) }, new[] { 1.0f, 0.0f }, 0); + TestAssignments(new[] { (true, 1.0f), (false, 0.0f) }, new[] { 1.0f, 0.0f }, 1); + } void TestAssignments( (bool, float)[] assignments, float[] expectedAssignments, int? defaultIndex = null, - Action> customize = null + Action> customize = null, + float? overrideExpectedDefaultValue = null ) { var root = CreateRoot("root"); @@ -98,6 +107,9 @@ namespace UnitTests.ReactiveComponent.ParameterAssignment { Assert.AreEqual(expected, mami.Control.value); } + + var expectedDefaultValue = overrideExpectedDefaultValue ?? (defaultIndex.HasValue ? expectedAssignments[defaultIndex.Value] : 0); + Assert.AreEqual(expectedDefaultValue, avDesc.expressionParameters.parameters.Single().defaultValue); } } } \ No newline at end of file diff --git a/UnitTests~/ReactiveComponent/ParameterAssignment/ParameterTypeTests.cs b/UnitTests~/ReactiveComponent/ParameterAssignment/ParameterTypeTests.cs new file mode 100644 index 00000000..6a4a45b5 --- /dev/null +++ b/UnitTests~/ReactiveComponent/ParameterAssignment/ParameterTypeTests.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using modular_avatar_tests; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using NUnit.Framework; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using VRC.SDK3.Avatars.ScriptableObjects; + +namespace UnitTests.ReactiveComponent.ParameterAssignment +{ + public class ParameterTypeTests : TestBase + { + [Test] + public void BoolTest() + { + Test(new[] { 0.0f }, VRCExpressionParameters.ValueType.Bool); + Test(new[] { 1.0f }, VRCExpressionParameters.ValueType.Bool); + + Test(new[] { 0.0f, 1.0f }, VRCExpressionParameters.ValueType.Bool); + Test(new[] { 1.0f, 0.0f }, VRCExpressionParameters.ValueType.Bool); + } + + [Test] + public void IntTest() + { + Test(new[] { 2.0f }, VRCExpressionParameters.ValueType.Int); + Test(new[] { 3.0f }, VRCExpressionParameters.ValueType.Int); + + Test(new[] { 0.0f, 1.0f, 2.0f }, VRCExpressionParameters.ValueType.Int); + Test(new[] { 2.0f, 1.0f, 0.0f }, VRCExpressionParameters.ValueType.Int); + + Test(new[] { 253.0f, 254.0f, 255.0f }, VRCExpressionParameters.ValueType.Int); + Test(new[] { 255.0f, 254.0f, 253.0f }, VRCExpressionParameters.ValueType.Int); + } + + [Test] + public void FloatTest() + { + Test(new[] { -1.0f }, VRCExpressionParameters.ValueType.Float); + Test(new[] { -0.1f }, VRCExpressionParameters.ValueType.Float); + Test(new[] { 0.1f }, VRCExpressionParameters.ValueType.Float); + Test(new[] { 0.9f }, VRCExpressionParameters.ValueType.Float); + + Test(new[] { -1.0f, 0.0f, 1.0f }, VRCExpressionParameters.ValueType.Float); + Test(new[] { 1.0f, 0.0f, -1.0f }, VRCExpressionParameters.ValueType.Float); + + Test(new[] { -0.1f, 0.0f, 0.1f }, VRCExpressionParameters.ValueType.Float); + Test(new[] { 0.1f, 0.0f, -0.1f }, VRCExpressionParameters.ValueType.Float); + + Test(new[] { 0.0f, 0.1f, 0.2f }, VRCExpressionParameters.ValueType.Float); + Test(new[] { 0.2f, 0.1f, 0.0f }, VRCExpressionParameters.ValueType.Float); + + Test(new[] { 0.7f, 0.8f, 0.9f }, VRCExpressionParameters.ValueType.Float); + Test(new[] { 0.9f, 0.8f, 0.7f }, VRCExpressionParameters.ValueType.Float); + } + + private void Test(float[] values, VRCExpressionParameters.ValueType expected) + { + var root = CreateRoot("Root"); + var descriptor = root.GetComponent(); + descriptor.expressionParameters = ScriptableObject.CreateInstance(); + descriptor.expressionParameters.parameters = Array.Empty(); + + for (var i = 0; i < values.Length; i++) + { + var obj = CreateChild(root, i.ToString()); + var mami = obj.AddComponent(); + mami.Control = new VRCExpressionsMenu.Control() + { + name = obj.name, + type = VRCExpressionsMenu.Control.ControlType.Toggle, + value = values[i], + parameter = new() { name = "Test" }, + }; + } + + var context = new nadena.dev.ndmf.BuildContext(root, null); + new ParameterAssignerPass().TestExecute(context); + + Assert.AreEqual(expected, descriptor.expressionParameters.parameters.Single().valueType); + } + } +} \ No newline at end of file diff --git a/UnitTests~/ReactiveComponent/ParameterAssignment/ParameterTypeTests.cs.meta b/UnitTests~/ReactiveComponent/ParameterAssignment/ParameterTypeTests.cs.meta new file mode 100644 index 00000000..e8b62222 --- /dev/null +++ b/UnitTests~/ReactiveComponent/ParameterAssignment/ParameterTypeTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c7051ac5a4bbe084c9d34c01c523eda3 +timeCreated: 1726276608 \ No newline at end of file diff --git a/UnitTests~/ReactiveComponent/ShapeDeletionAnalysis.cs b/UnitTests~/ReactiveComponent/ShapeDeletionAnalysis.cs new file mode 100644 index 00000000..7cb78e50 --- /dev/null +++ b/UnitTests~/ReactiveComponent/ShapeDeletionAnalysis.cs @@ -0,0 +1,98 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using modular_avatar_tests; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using NUnit.Framework; +using UnityEngine; + +public class ShapeDeletionAnalysis : TestBase +{ + [Test] + public void BasicShapeDeletionAnalysis() + { + var root = CreatePrefab("DeletionTest.prefab"); + + var mesh = AssertPreviewDeletion(root); + + AssertBuildDeletion(mesh, root); + } + + [Test] + public void WhenShapeDeletionIsConditionedOnSubsequentChanger_DoesNotDelete() + { + var root = CreatePrefab("DeletionTest.prefab"); + root.transform.Find("MenuSet").gameObject.SetActive(true); + + AssertPreviewDeletion(root); + AssertNoMeshDeletion(root); + + var mesh = root.GetComponentInChildren(); + Assert.AreEqual(100, mesh.GetBlendShapeWeight(mesh.sharedMesh.GetBlendShapeIndex("bottom"))); + } + + + [Test] + public void WhenShapeDeletionIsConditionedOnItself_DoesNotDelete() + { + var root = CreatePrefab("DeletionTest.prefab"); + root.transform.Find("Delete").gameObject.AddComponent().InitSettings(); + + AssertNoPreviewDeletion(root); + AssertNoMeshDeletion(root); + + var mesh = root.GetComponentInChildren(); + // deletion action is initially off, so we use the shape changer above it, which is set to 50. + Assert.AreEqual(50f, mesh.GetBlendShapeWeight(mesh.sharedMesh.GetBlendShapeIndex("bottom"))); + } + + private static void AssertBuildDeletion(SkinnedMeshRenderer mesh, GameObject root) + { + var originalSharedMesh = mesh.sharedMesh; + AvatarProcessor.ProcessAvatar(root); + Assert.AreNotEqual(originalSharedMesh, mesh.sharedMesh); + + Assert.IsTrue(mesh.sharedMesh.vertices.All(v => v.z >= 0)); + } + + private static SkinnedMeshRenderer AssertPreviewDeletion(GameObject root) + { + var mesh = root.GetComponentInChildren(); + var analysis = new ReactiveObjectAnalyzer().Analyze(root); + var deletedShape = analysis.Shapes.GetValueOrDefault(new TargetProp() + { + TargetObject = mesh, + PropertyName = "deletedShape.bottom" + }); + Assert.IsNotNull(deletedShape); + var activeGroup = deletedShape.actionGroups.LastOrDefault(ag => ag.InitiallyActive); + Assert.AreEqual(1.0f, activeGroup?.Value); + return mesh; + } + + private static void AssertNoPreviewDeletion(GameObject root) + { + var mesh = root.GetComponentInChildren(); + var analysis = new ReactiveObjectAnalyzer().Analyze(root); + var deletedShape = analysis.Shapes.GetValueOrDefault(new TargetProp() + { + TargetObject = mesh, + PropertyName = "deletedShape.bottom" + }); + if (deletedShape != null) + { + var activeGroup = deletedShape.actionGroups.LastOrDefault(ag => ag.InitiallyActive); + Assert.IsFalse(activeGroup?.Value is float f && f > 0); + } + + } + + private static void AssertNoMeshDeletion(GameObject root) + { + var mesh = root.GetComponentInChildren(); + var originalSharedMesh = mesh.sharedMesh; + AvatarProcessor.ProcessAvatar(root); + Assert.AreEqual(originalSharedMesh, mesh.sharedMesh); + } +} diff --git a/UnitTests~/ReactiveComponent/ShapeDeletionAnalysis.cs.meta b/UnitTests~/ReactiveComponent/ShapeDeletionAnalysis.cs.meta new file mode 100644 index 00000000..024b7f11 --- /dev/null +++ b/UnitTests~/ReactiveComponent/ShapeDeletionAnalysis.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 18eb55e1b66a00243a91142456dfd5f5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/ReactiveComponent/shape deletion test mesh.fbx b/UnitTests~/ReactiveComponent/shape deletion test mesh.fbx new file mode 100644 index 00000000..52fa8bb9 Binary files /dev/null and b/UnitTests~/ReactiveComponent/shape deletion test mesh.fbx differ diff --git a/UnitTests~/ReactiveComponent/shape deletion test mesh.fbx.meta b/UnitTests~/ReactiveComponent/shape deletion test mesh.fbx.meta new file mode 100644 index 00000000..0f60ec10 --- /dev/null +++ b/UnitTests~/ReactiveComponent/shape deletion test mesh.fbx.meta @@ -0,0 +1,109 @@ +fileFormatVersion: 2 +guid: fe5b76dae94c07345b74d51e9a9a8440 +ModelImporter: + serializedVersion: 22200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + rigImportErrors: + rigImportWarnings: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 1 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimation.cs b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimation.cs index 13be9ca7..c1061b3e 100644 --- a/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimation.cs +++ b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimation.cs @@ -14,7 +14,67 @@ namespace ShapeChangerTests [Test] public void SetsCorrectInitialStatesAndAnimations() { - var root = CreatePrefab("SCDefaultAnimation.prefab"); + SetsCorrectInitialStatesAndAnimations("SCDefaultAnimation.prefab"); + } + + [Test] + public void SetsCorrectInitialStatesAndAnimationsForInactiveSC() + { + var root = CreatePrefab("SCDefaultAnimationInactive.prefab"); + AvatarProcessor.ProcessAvatar(root); + + var fx = (AnimatorController) FindFxController(root).animatorController; + var baseLayer = fx.layers[0]; + + var bt = baseLayer.stateMachine.states[0].state.motion as BlendTree; + Assert.NotNull(bt); + var subBt = bt.children[0].motion as BlendTree; + Assert.NotNull(subBt); + var clip = subBt.children[0].motion as AnimationClip; + Assert.NotNull(clip); + + var smr = root.transform.Find("test mesh").GetComponent(); + var sharedMesh = smr.sharedMesh; + + var bindings = AnimationUtility.GetCurveBindings(clip); + var curve = AnimationUtility.GetEditorCurve(clip, EditorCurveBinding.FloatCurve( + "test mesh", + typeof(SkinnedMeshRenderer), + "blendShape.key1" + )); + Assert.IsNull(curve); // always off MenuItem (due to object disable), no curve should be generated + + curve = AnimationUtility.GetEditorCurve(clip, EditorCurveBinding.FloatCurve( + "test mesh", + typeof(SkinnedMeshRenderer), + "blendShape.key2" + )); + // Always-on delete, no curve should be generated + Assert.IsNull(curve); + + curve = AnimationUtility.GetEditorCurve(clip, EditorCurveBinding.FloatCurve( + "test mesh", + typeof(SkinnedMeshRenderer), + "blendShape.key3" + )); + // Always-on set, no curve should be generated + Assert.IsNull(curve); + + // Check actual blendshape states + Assert.AreEqual(10.0f, smr.GetBlendShapeWeight(sharedMesh.GetBlendShapeIndex("key1")), 0.1f); + Assert.AreEqual(5.0f, smr.GetBlendShapeWeight(sharedMesh.GetBlendShapeIndex("key2")), 0.1f); + Assert.AreEqual(100.0f, smr.GetBlendShapeWeight(sharedMesh.GetBlendShapeIndex("key3")), 0.1f); + } + + [Test] + public void SetsCorrectInitialStatesAndAnimationsForInvertedSC() + { + SetsCorrectInitialStatesAndAnimations("SCDefaultAnimationInverted.prefab"); + } + + private void SetsCorrectInitialStatesAndAnimations(string prefabPath) + { + var root = CreatePrefab(prefabPath); AvatarProcessor.ProcessAvatar(root); var fx = (AnimatorController) FindFxController(root).animatorController; diff --git a/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimation.prefab b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimation.prefab index f6431564..6dbfb2b7 100644 --- a/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimation.prefab +++ b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimation.prefab @@ -46,10 +46,13 @@ MonoBehaviour: m_EditorClassIdentifier: m_inverted: 0 m_targetRenderer: - referencePath: test mesh + referencePath: targetObject: {fileID: 0} m_shapes: - - ShapeName: key2 + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key2 ChangeType: 0 Value: 100 --- !u!1 &2598725701317979415 @@ -98,10 +101,13 @@ MonoBehaviour: m_EditorClassIdentifier: m_inverted: 0 m_targetRenderer: - referencePath: test mesh + referencePath: targetObject: {fileID: 0} m_shapes: - - ShapeName: key1 + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key1 ChangeType: 1 Value: 10 --- !u!1 &2845086157653980983 @@ -150,10 +156,13 @@ MonoBehaviour: m_EditorClassIdentifier: m_inverted: 0 m_targetRenderer: - referencePath: test mesh + referencePath: targetObject: {fileID: 0} m_shapes: - - ShapeName: key3 + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key3 ChangeType: 1 Value: 100 --- !u!1 &6385483934583485188 @@ -174,7 +183,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 0 + m_IsActive: 1 --- !u!4 &569625391371299408 Transform: m_ObjectHideFlags: 0 @@ -204,10 +213,13 @@ MonoBehaviour: m_EditorClassIdentifier: m_inverted: 0 m_targetRenderer: - referencePath: test mesh + referencePath: targetObject: {fileID: 0} m_shapes: - - ShapeName: key1 + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key1 ChangeType: 1 Value: 20 --- !u!114 &2918390808850211981 @@ -238,6 +250,7 @@ MonoBehaviour: isSynced: 1 isSaved: 1 isDefault: 0 + automaticValue: 0 --- !u!114 &664065153831629983 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInactive.prefab b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInactive.prefab new file mode 100644 index 00000000..e7640290 --- /dev/null +++ b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInactive.prefab @@ -0,0 +1,668 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1307328145036867423 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7471115643889882934} + - component: {fileID: 2015798673852064281} + m_Layer: 0 + m_Name: AlwaysOffDelete + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &7471115643889882934 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1307328145036867423} + 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: 8936933457054072598} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &2015798673852064281 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1307328145036867423} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 1 + m_shapes: + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key2 + ChangeType: 0 + Value: 100 +--- !u!1 &2598725701317979415 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1861262250770563182} + - component: {fileID: 8866671501173891171} + m_Layer: 0 + m_Name: Toggled + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &1861262250770563182 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2598725701317979415} + 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: 8936933457054072598} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8866671501173891171 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2598725701317979415} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 1 + m_shapes: + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key1 + ChangeType: 1 + Value: 10 +--- !u!1 &2845086157653980983 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 110069860838053623} + - component: {fileID: 8218581995269956798} + m_Layer: 0 + m_Name: AlwaysOffSet + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &110069860838053623 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2845086157653980983} + 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: 8936933457054072598} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8218581995269956798 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2845086157653980983} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 1 + m_shapes: + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key3 + ChangeType: 1 + Value: 100 +--- !u!1 &6385483934583485188 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 569625391371299408} + - component: {fileID: 3841502665919975468} + - component: {fileID: 2918390808850211981} + - component: {fileID: 664065153831629983} + m_Layer: 0 + m_Name: InitialOffToggled + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &569625391371299408 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6385483934583485188} + 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: 8936933457054072598} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &3841502665919975468 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6385483934583485188} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 0 + m_shapes: + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key1 + ChangeType: 1 + Value: 20 +--- !u!114 &2918390808850211981 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6385483934583485188} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3b29d45007c5493d926d2cd45a489529, type: 3} + m_Name: + m_EditorClassIdentifier: + Control: + name: + 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: 0 +--- !u!114 &664065153831629983 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6385483934583485188} + 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 &6855505756433160176 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8936933457054072598} + - component: {fileID: 6580323041906195452} + - component: {fileID: 146169679456758165} + - component: {fileID: 5146811121193962360} + m_Layer: 0 + m_Name: SCDefaultAnimationInactive + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8936933457054072598 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6855505756433160176} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.0024816, y: 0.25853348, z: -0.63345385} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1861262250770563182} + - {fileID: 7471115643889882934} + - {fileID: 110069860838053623} + - {fileID: 569625391371299408} + - {fileID: 1326682634762807916} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &6580323041906195452 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6855505756433160176} + 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 &146169679456758165 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6855505756433160176} + 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: 0 + 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: 0 + 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: 0} + mask: {fileID: 0} + isDefault: 1 + 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 &5146811121193962360 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6855505756433160176} + 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!1001 &1577363430154308999 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 8936933457054072598} + m_Modifications: + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalPosition.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalRotation.w + value: 0.7071067 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalRotation.x + value: -0.7071068 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_DirtyAABB + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_AABB.m_Extent.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_AABB.m_Extent.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_BlendShapeWeights.Array.data[0] + value: 5 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_BlendShapeWeights.Array.data[1] + value: 6 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_BlendShapeWeights.Array.data[2] + value: 7 + objectReference: {fileID: 0} + - target: {fileID: 919132149155446097, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_Name + value: test mesh + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} +--- !u!4 &1326682634762807916 stripped +Transform: + m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + m_PrefabInstance: {fileID: 1577363430154308999} + m_PrefabAsset: {fileID: 0} diff --git a/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInactive.prefab.meta b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInactive.prefab.meta new file mode 100644 index 00000000..d37cf9c1 --- /dev/null +++ b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInactive.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4744d7b0db7db0d459f3aa7e6a0cf7db +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInverted.prefab b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInverted.prefab new file mode 100644 index 00000000..daa2600d --- /dev/null +++ b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInverted.prefab @@ -0,0 +1,668 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1307328145036867423 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7471115643889882934} + - component: {fileID: 2015798673852064281} + m_Layer: 0 + m_Name: AlwaysOffDelete + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &7471115643889882934 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1307328145036867423} + 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: 8936933457054072598} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &2015798673852064281 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1307328145036867423} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 1 + m_shapes: + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key2 + ChangeType: 0 + Value: 100 +--- !u!1 &2598725701317979415 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1861262250770563182} + - component: {fileID: 8866671501173891171} + m_Layer: 0 + m_Name: Toggled + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &1861262250770563182 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2598725701317979415} + 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: 8936933457054072598} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8866671501173891171 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2598725701317979415} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 1 + m_shapes: + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key1 + ChangeType: 1 + Value: 10 +--- !u!1 &2845086157653980983 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 110069860838053623} + - component: {fileID: 8218581995269956798} + m_Layer: 0 + m_Name: AlwaysOffSet + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!4 &110069860838053623 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2845086157653980983} + 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: 8936933457054072598} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8218581995269956798 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2845086157653980983} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 1 + m_shapes: + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key3 + ChangeType: 1 + Value: 100 +--- !u!1 &6385483934583485188 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 569625391371299408} + - component: {fileID: 3841502665919975468} + - component: {fileID: 2918390808850211981} + - component: {fileID: 664065153831629983} + m_Layer: 0 + m_Name: InitialOnToggled + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &569625391371299408 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6385483934583485188} + 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: 8936933457054072598} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &3841502665919975468 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6385483934583485188} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2db441f589c3407bb6fb5f02ff8ab541, type: 3} + m_Name: + m_EditorClassIdentifier: + m_inverted: 1 + m_shapes: + - Object: + referencePath: test mesh + targetObject: {fileID: 0} + ShapeName: key1 + ChangeType: 1 + Value: 20 +--- !u!114 &2918390808850211981 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6385483934583485188} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3b29d45007c5493d926d2cd45a489529, type: 3} + m_Name: + m_EditorClassIdentifier: + Control: + name: + 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: 1 + automaticValue: 0 +--- !u!114 &664065153831629983 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6385483934583485188} + 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 &6855505756433160176 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8936933457054072598} + - component: {fileID: 6580323041906195452} + - component: {fileID: 146169679456758165} + - component: {fileID: 5146811121193962360} + m_Layer: 0 + m_Name: SCDefaultAnimationInverted + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8936933457054072598 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6855505756433160176} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.0024816, y: 0.25853348, z: -0.63345385} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1861262250770563182} + - {fileID: 7471115643889882934} + - {fileID: 110069860838053623} + - {fileID: 569625391371299408} + - {fileID: 1326682634762807916} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!95 &6580323041906195452 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6855505756433160176} + 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 &146169679456758165 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6855505756433160176} + 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: 0 + 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: 0 + 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: 0} + mask: {fileID: 0} + isDefault: 1 + 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 &5146811121193962360 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6855505756433160176} + 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!1001 &1577363430154308999 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 8936933457054072598} + m_Modifications: + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalPosition.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalRotation.w + value: 0.7071067 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalRotation.x + value: -0.7071068 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_DirtyAABB + value: 0 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_AABB.m_Extent.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_AABB.m_Extent.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_BlendShapeWeights.Array.data[0] + value: 5 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_BlendShapeWeights.Array.data[1] + value: 6 + objectReference: {fileID: 0} + - target: {fileID: -3887185075125053422, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_BlendShapeWeights.Array.data[2] + value: 7 + objectReference: {fileID: 0} + - target: {fileID: 919132149155446097, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + propertyPath: m_Name + value: test mesh + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} +--- !u!4 &1326682634762807916 stripped +Transform: + m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: cd28f61dacdc2424d951194ff69ba154, type: 3} + m_PrefabInstance: {fileID: 1577363430154308999} + m_PrefabAsset: {fileID: 0} diff --git a/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInverted.prefab.meta b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInverted.prefab.meta new file mode 100644 index 00000000..487346d8 --- /dev/null +++ b/UnitTests~/ShapeChanger/InitialStates/SCDefaultAnimationInverted.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c86e54d4b828d364aa677a5b3ce7be12 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/VirtualMenuTests/VirtualMenuTests.cs b/UnitTests~/VirtualMenuTests/VirtualMenuTests.cs index 81a9ffa3..5450dba0 100644 --- a/UnitTests~/VirtualMenuTests/VirtualMenuTests.cs +++ b/UnitTests~/VirtualMenuTests/VirtualMenuTests.cs @@ -698,6 +698,32 @@ namespace modular_avatar_tests.VirtualMenuTests Assert.AreEqual(4, virtualMenu.RootMenuNode.Controls[0].SubmenuNode.Controls[5].subParameters.Length); } + [Test] + public void MergeArmatureAndMenuInstallerOnSameObjectWorks() + { + var root = CreateRoot("root"); + var armature = CreateChild(root, "Armature"); + var installer = armature.AddComponent(); + + var merge = installer.gameObject.AddComponent(); + merge.mergeTarget.Set(root); + + var menu = Create(); + menu.controls.Add(new VRCExpressionsMenu.Control() + { + name = "control", + type = VRCExpressionsMenu.Control.ControlType.Toggle + }); + + installer.menuToAppend = menu; + + AvatarProcessor.ProcessAvatar(root); + + var realizedMenu = root.GetComponent().expressionsMenu; + Assert.AreEqual(1, realizedMenu.controls.Count); + Assert.AreEqual("control", realizedMenu.controls[0].name); + } + ModularAvatarMenuInstaller CreateInstaller(string name) { GameObject obj = new GameObject(); diff --git a/docs-site~/yarn.lock b/docs-site~/yarn.lock index 560bf271..8a4071ab 100644 --- a/docs-site~/yarn.lock +++ b/docs-site~/yarn.lock @@ -1413,9 +1413,9 @@ __metadata: linkType: hard "path-to-regexp@npm:^6.2.0": - version: 6.2.1 - resolution: "path-to-regexp@npm:6.2.1" - checksum: 10/1e266be712d1a08086ee77beab12a1804842ec635dfed44f9ee1ba960a0e01cec8063fb8c92561115cdc0ce73158cdc7766e353ffa039340b4a85b370084c4d4 + version: 6.3.0 + resolution: "path-to-regexp@npm:6.3.0" + checksum: 10c0/73b67f4638b41cde56254e6354e46ae3a2ebc08279583f6af3d96fe4664fc75788f74ed0d18ca44fa4a98491b69434f9eee73b97bb5314bd1b5adb700f5c18d6 languageName: node linkType: hard diff --git a/docs~/docs/distributing-prefabs/for-outfit-creators/index.md b/docs~/docs/distributing-prefabs/for-outfit-creators/index.md index fbb609d3..d814fbd0 100644 --- a/docs~/docs/distributing-prefabs/for-outfit-creators/index.md +++ b/docs~/docs/distributing-prefabs/for-outfit-creators/index.md @@ -5,6 +5,15 @@ compatible with Modular Avatar. Generally, Modular Avatar has been designed to w seen in outfits designed for avatar use, but there is the occasional outfit which does something a bit different. This page may help you understand what Modular Avatar expects, and how to ensure that your outfit works with it. +## tl;dr + +For "modular avatar compatible" outfits, just make sure Setup Outfit works. For "Modular Avatar Preset" outfits, +run "setup outfit" ahead of time, and also consider setting up: + +- Blendshape Sync (to sync body-shape-altering blendshapes from the base avatar) +- Shape Changer (to shrink or hide parts of the base mesh when parts the outfit is enabled) +- Menu Items and Object Toggle (to allow users to toggle parts of the outfit on/off) + ## Compatible vs Preset Before we get started, we should clarify that there are two different levels of support for Modular Avatar. At the @@ -243,6 +252,12 @@ Guidelines](../logo-usage.md) for detailed requirements. You can however go a step further and set up additional things to make things more convenient for your customers. Here's some examples. +### Blendshape Sync + +Some avatars have blendshapes which change the size of various portions of the body. You can use the Blendshape Sync +component to automatically adjust the outfit's blendshapes to match those of the base avatar. This can be useful for +breast size blendshapes, for example. + ### Shrink/hide shapekeys Many avatars have shape keys which can be used to shrink and hide parts of the base mesh, to avoid clipping into the @@ -252,8 +267,13 @@ shrink or hide shape keys when the corresponding object is enabled. This will also respond to animations which turn on/off parts of the outfit, and can even outright _delete_ portions of the mesh, if the Shape Changer object is always enabled. -### Blendshape Sync +### Object Toggles -Some avatars have blendshapes which change the size of various portions of the body. You can use the Blendshape Sync -component to automatically adjust the outfit's blendshapes to match those of the base avatar. This can be useful for -breast size blendshapes, for example. +Many outfits have different parts that can be turned on and off. You can set up a menu for your users to use ahead of +time by configuring `Object Toggle`s. Take a look at [the Object Toggle tutorial](../../tutorials/object_toggle/index.md) +for details on how to set these up. + +You can then combine these into a single submenu; to do this, set up an object as the parent of your toggles that looks +like this: + +![Menu item grouping](menu-item-group.png) \ No newline at end of file diff --git a/docs~/docs/distributing-prefabs/for-outfit-creators/menu-item-group.png b/docs~/docs/distributing-prefabs/for-outfit-creators/menu-item-group.png new file mode 100644 index 00000000..2498ee1d Binary files /dev/null and b/docs~/docs/distributing-prefabs/for-outfit-creators/menu-item-group.png differ diff --git a/docs~/docs/reference/mesh-settings.md b/docs~/docs/reference/mesh-settings.md index b506fb1f..b7c255e1 100644 --- a/docs~/docs/reference/mesh-settings.md +++ b/docs~/docs/reference/mesh-settings.md @@ -23,10 +23,18 @@ designed for a specific avatar. ## Manually configuring Mesh Settings -When you add Mesh Settings to a game object, initially it is configured to do nothing. By setting either -"Anchor Override Mode" or "Bounds Override Mode" to "Set", you can configure the anchor override or bounds -for all meshes under that game object. Alternately, by setting the mode to "Don't set", you can exclude -these meshes from the influence of Mesh Settings higher up on the hierarchy. +When you add Mesh Settings to a game object, initially it is configured to do nothing. In order for the settings +component to have any effect, you need to change the "Anchor Override Mode" and/or "Bounds Override Mode". These +support the following options: + +- Inherit: This component does nothing for this setting; it will inherit values set in parent Mesh Settings. +- Set: This component sets the corresponding setting on any meshes on its GameObject or its children. +- Don't set: This component _blocks_ any parent Mesh Settings from having an effect. Meshes will remain at their default + settings. +- Set or inherit: If there is a parent Mesh Settings in Set mode, it will be used. If no parent Mesh Settings applies, + then this component's settings will be used. This is useful for outfit prefabs, to ensure that any avatar-wide + settings + take precedence. When configuring bounds, the bounding box will be determined relative to the transform you specify as the "Root Bone". Note that bounds only affects Skinned Mesh Renderers, but Anchor Override also impacts other diff --git a/docs~/docs/reference/reaction/debugger/index.md b/docs~/docs/reference/reaction/debugger/index.md index 6cbfbf47..2eae1191 100644 --- a/docs~/docs/reference/reaction/debugger/index.md +++ b/docs~/docs/reference/reaction/debugger/index.md @@ -1,5 +1,4 @@ - ---- +--- sidebar_position: 900 --- diff --git a/docs~/docs/reference/visible-head-accessory.md b/docs~/docs/reference/visible-head-accessory.md index c755e5f2..e6383ce1 100644 --- a/docs~/docs/reference/visible-head-accessory.md +++ b/docs~/docs/reference/visible-head-accessory.md @@ -22,5 +22,3 @@ Just attach a Visible Head Accessory component under a child of the Head bone. T The component will automatically generate a clone of the Head bone, which is connected to the real head bone using a parent constraint. Only one constraint will be generated, even if multiple Visible Head Accessory components are used. As such, the performance impact of this component is the same whether you use one or dozens. - -Due to technical limitations on the Quest, this component has no effect when building for Quest standalone. diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/distributing-prefabs/for-outfit-creators/index.md b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/distributing-prefabs/for-outfit-creators/index.md index c5ace509..ec24fa82 100644 --- a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/distributing-prefabs/for-outfit-creators/index.md +++ b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/distributing-prefabs/for-outfit-creators/index.md @@ -4,7 +4,16 @@ 基本的には、Modular Avatarは既存の衣装の設計に合わせて動作するように設計されていますが、たまには仕様から逸脱するものもあります。 このページにはModular Avatarが期待している設計と、その設計に合わせるための詳細な要件をまとめています。 -## 「対応」 vs 「設定済み」 +## 忙しい人のための要約 + +「Modular Avatar対応」の衣装を作りたいならば、「Setup Outfit」が正常に動作することを確認してください。 +「Modular Avatar設定済み」の衣装を作りたいならば、あらかじめ「Setup Outfit」を実行してください。さらに、以下の設定もご検討ください。 + +- Blendshape Sync(素体の体型変更に合わせて衣装のブレンドシェイプを自動変更) +- Shape Changer(衣装のON/OFFに応じて、素体の一部を縮小または非表示にするため) +- Menu Item と Object Toggle(ユーザーが衣装の一部をON/OFFできるようにするため) + +## 「対応」 と 「設定済み」 始める前に、まずはModular Avatar向けの衣装には二つの対応レベルがあるということを理解しておきましょう。 最低限、ボーンの命名規則やPhysBonesコンポーネントの配置がModular Avatarが期待するものと互換性があることを確認することで、 @@ -220,6 +229,12 @@ Modular Avatarをサポートするための最低限の要件について話し ただし、さらに一歩進んで、お客様にとってより便利な設定を行うことができます。以下にいくつかの例を示します。 +### ブレンドシェイプ同期 + +一部のアバターには、体のさまざまな部分のサイズを変更するブレンドシェイプが含まれています。このような場合、衣装のブレンドシェイプを +自動的に操作して、基本アバターと一致させることができます。例えば、胸のサイズを変更するブレンドシェイプがある場合、 +この機能を使用して衣装のブレンドシェイプを自動的に調整することができます。 + ### 縮小・非表示用のシェープキー 多くのアバターには、素体メッシュの一部を縮小したり非表示にしたりするためのシェープキーが含まれています。これにより、衣装が素体メッシュに @@ -229,8 +244,11 @@ Avatarは対応するオブジェクトが有効になると、 シェープキーを自動的に縮小または非表示にします。また、アニメーションによって衣装の一部がオン/オフになる場合にも応答し、 Shape Changerオブジェクトが常に有効であれば、メッシュの一部を削除することさえできます。 -### ブレンドシェイプ同期 +### オブジェクトのスイッチ -一部のアバターには、体のさまざまな部分のサイズを変更するブレンドシェイプが含まれています。このような場合、衣装のブレンドシェイプを -自動的に操作して、基本アバターと一致させることができます。例えば、胸のサイズを変更するブレンドシェイプがある場合、 -この機能を使用して衣装のブレンドシェイプを自動的に調整することができます。 +多くの衣装には、ON/OFFできるパーツがあります。`Object Toggle`をあらかじめ設定することで、ユーザーに設定済みメニューを提供できます。 +詳しくは[Object Toggleのチュートリアル](../../tutorials/object_toggle/index.md)にご参照ください。 + +また、一つのサブメニューにまとめることもできます。トグルオブジェクトの親に、このような設定のオブジェクトを用意しましょう。 + +![Menu item grouping](menu-item-group.png) \ No newline at end of file diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/distributing-prefabs/for-outfit-creators/menu-item-group.png b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/distributing-prefabs/for-outfit-creators/menu-item-group.png new file mode 100644 index 00000000..56383a93 Binary files /dev/null and b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/distributing-prefabs/for-outfit-creators/menu-item-group.png differ diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/mesh-settings.md b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/mesh-settings.md index e0d96bf7..12459d0f 100644 --- a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/mesh-settings.md +++ b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/mesh-settings.md @@ -21,9 +21,16 @@ Mesh Settingsコンポーネントを使用すると、特定のゲームオブ ## Mesh Settingsの手動設定 -ゲームオブジェクトにMesh Settingsを追加すると、最初は何も効果がありません。Anchor Overrideの設定モードまたはBounds Override -の設定モードを「設定」に設定することで、そのゲームオブジェクトの下にあるすべてのメッシュのアンカーオーバーライドまたはバウンズ -を設定できます。また、モードを「設定しない」に設定することで、これらのメッシュを階層の上位にあるMesh Settingsの影響から除外できます。 +ゲームオブジェクトにMesh Settingsを追加すると、最初は何も効果がありません。効果を発揮するには、まずは「Anchor Override +設定」あるいは +「Bounds 設定」で「設定モード」を変更する必要があります。以下の選択しがあります。 + +- 継承:このコンポーネントはこの設定に対して何もしません。親のMesh Settingsで設定された値を継承します。 +- 設定:このコンポーネントは、そのゲームオブジェクトとその子にあるメッシュの対応する設定を設定します。 +- 設定しない:このコンポーネントは、親のMesh Settingsの影響を受けないようにします。メッシュはデフォルトの設定のままです。 +- 親が継承された時は継承、または設定:親のMesh Settingsが設定モードにある場合、それが使用されます。親のMesh + Settingsが適用されない場合、 + このコンポーネントの設定が使用されます。衣装プレハブなどに、アバター全体の設定が優先されるようにするために便利です。 バウンズを設定する場合、バウンディングボックスは「Root Bone」として指定したトランスフォームに対して相対的に決定されます。 また、バウンズはSkinned Mesh Rendererのみに影響しますが、Anchor OverrideはMesh RendererやLine Rendererなどの他のタイプの diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/visible-head-accessory.md b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/visible-head-accessory.md index 6fbf09a6..8f2d2dcf 100644 --- a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/visible-head-accessory.md +++ b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/visible-head-accessory.md @@ -22,5 +22,3 @@ Headボーンの子にVisible Head Accessoryコンポーネントを付けるだ このコンポーネントは自動的にHeadボーンを複製し、Parent Constraintで本物のHeadボーンに追従させます。 複数のVisible Head Accessoryコンポーネントを使っても、Constraintは一つだけです。なので、複数のボーンを指定しても、その分重くなることはありません。 - -技術的な制約により、Quest単体では動作できません。Quest向けのビルドはつけたままにしてもいいが、効果は発揮しません。 diff --git a/docs~/src/css/custom.css b/docs~/src/css/custom.css index c7fa3eb1..4ce92276 100644 --- a/docs~/src/css/custom.css +++ b/docs~/src/css/custom.css @@ -64,4 +64,24 @@ img { aspect-ratio: auto; height: auto; +} + +.discordLink { + display: block; + height: 2em; + object-fit: cover; + padding: 0.1em; +} + +img.button { + /*aspect-ratio: auto;*/ + + height: 100%; + object-fit: cover; + padding: 0; + margin: 0; +} + +.button-group + .button-group { + margin-top: 1em; } \ No newline at end of file diff --git a/docs~/src/pages/index.module.css b/docs~/src/pages/index.module.css index a8166690..9ef0a6aa 100644 --- a/docs~/src/pages/index.module.css +++ b/docs~/src/pages/index.module.css @@ -23,6 +23,7 @@ justify-content: center; column-gap: 2rem; row-gap: 1rem; + margin-bottom: 1em; } @media screen and (max-width: 996px) { @@ -36,4 +37,4 @@ div.logo { text-align: center; display: flex; justify-content: center; -} \ No newline at end of file +} diff --git a/docs~/src/pages/index.tsx b/docs~/src/pages/index.tsx index 50dd02de..889f8ced 100644 --- a/docs~/src/pages/index.tsx +++ b/docs~/src/pages/index.tsx @@ -29,7 +29,7 @@ function HomepageHeader() {

Drag-and-Drop Avatar Assembly

-
+
Tutorials
+
+ + Discord + +
); } export default function Home(): JSX.Element { - const {siteConfig} = useDocusaurusContext(); - return ( - - -
- -
-
+ const {siteConfig} = useDocusaurusContext(); + return ( + + +
+ +
+
); } diff --git a/docs~/yarn.lock b/docs~/yarn.lock index 23f6d154..a5981967 100644 --- a/docs~/yarn.lock +++ b/docs~/yarn.lock @@ -3711,9 +3711,9 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.2": - version: 1.20.2 - resolution: "body-parser@npm:1.20.2" +"body-parser@npm:1.20.3": + version: 1.20.3 + resolution: "body-parser@npm:1.20.3" dependencies: bytes: 3.1.2 content-type: ~1.0.5 @@ -3723,11 +3723,11 @@ __metadata: http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.11.0 + qs: 6.13.0 raw-body: 2.5.2 type-is: ~1.6.18 unpipe: 1.0.0 - checksum: 14d37ec638ab5c93f6099ecaed7f28f890d222c650c69306872e00b9efa081ff6c596cd9afb9930656aae4d6c4e1c17537bea12bb73c87a217cb3cfea8896737 + checksum: 1a35c59a6be8d852b00946330141c4f142c6af0f970faa87f10ad74f1ee7118078056706a05ae3093c54dabca9cd3770fa62a170a85801da1a4324f04381167d languageName: node linkType: hard @@ -5059,6 +5059,13 @@ __metadata: languageName: node linkType: hard +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -5367,41 +5374,41 @@ __metadata: linkType: hard "express@npm:^4.17.3": - version: 4.19.2 - resolution: "express@npm:4.19.2" + version: 4.21.0 + resolution: "express@npm:4.21.0" dependencies: accepts: ~1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.2 + body-parser: 1.20.3 content-disposition: 0.5.4 content-type: ~1.0.4 cookie: 0.6.0 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 - encodeurl: ~1.0.2 + encodeurl: ~2.0.0 escape-html: ~1.0.3 etag: ~1.8.1 - finalhandler: 1.2.0 + finalhandler: 1.3.1 fresh: 0.5.2 http-errors: 2.0.0 - merge-descriptors: 1.0.1 + merge-descriptors: 1.0.3 methods: ~1.1.2 on-finished: 2.4.1 parseurl: ~1.3.3 - path-to-regexp: 0.1.7 + path-to-regexp: 0.1.10 proxy-addr: ~2.0.7 - qs: 6.11.0 + qs: 6.13.0 range-parser: ~1.2.1 safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 + send: 0.19.0 + serve-static: 1.16.2 setprototypeof: 1.2.0 statuses: 2.0.1 type-is: ~1.6.18 utils-merge: 1.0.1 vary: ~1.1.2 - checksum: 212dbd6c2c222a96a61bc927639c95970a53b06257080bb9e2838adb3bffdb966856551fdad1ab5dd654a217c35db94f987d0aa88d48fb04d306340f5f34dca5 + checksum: 1c5212993f665809c249bf00ab550b989d1365a5b9171cdfaa26d93ee2ef10cd8add520861ec8d5da74b3194d8374e1d9d53e85ef69b89fd9c4196b87045a5d4 languageName: node linkType: hard @@ -5521,18 +5528,18 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:1.2.0": - version: 1.2.0 - resolution: "finalhandler@npm:1.2.0" +"finalhandler@npm:1.3.1": + version: 1.3.1 + resolution: "finalhandler@npm:1.3.1" dependencies: debug: 2.6.9 - encodeurl: ~1.0.2 + encodeurl: ~2.0.0 escape-html: ~1.0.3 on-finished: 2.4.1 parseurl: ~1.3.3 statuses: 2.0.1 unpipe: ~1.0.0 - checksum: 92effbfd32e22a7dff2994acedbd9bcc3aa646a3e919ea6a53238090e87097f8ef07cced90aa2cc421abdf993aefbdd5b00104d55c7c5479a8d00ed105b45716 + checksum: a8c58cd97c9cd47679a870f6833a7b417043f5a288cd6af6d0f49b476c874a506100303a128b6d3b654c3d74fa4ff2ffed68a48a27e8630cda5c918f2977dcf4 languageName: node linkType: hard @@ -7586,10 +7593,10 @@ __metadata: languageName: node linkType: hard -"merge-descriptors@npm:1.0.1": - version: 1.0.1 - resolution: "merge-descriptors@npm:1.0.1" - checksum: 5abc259d2ae25bb06d19ce2b94a21632583c74e2a9109ee1ba7fd147aa7362b380d971e0251069f8b3eb7d48c21ac839e21fa177b335e82c76ec172e30c31a26 +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 52117adbe0313d5defa771c9993fe081e2d2df9b840597e966aadafde04ae8d0e3da46bac7ca4efc37d4d2b839436582659cd49c6a43eacb3fe3050896a105d1 languageName: node linkType: hard @@ -8869,10 +8876,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.7": - version: 0.1.7 - resolution: "path-to-regexp@npm:0.1.7" - checksum: 69a14ea24db543e8b0f4353305c5eac6907917031340e5a8b37df688e52accd09e3cebfe1660b70d76b6bd89152f52183f28c74813dbf454ba1a01c82a38abce +"path-to-regexp@npm:0.1.10": + version: 0.1.10 + resolution: "path-to-regexp@npm:0.1.10" + checksum: ab7a3b7a0b914476d44030340b0a65d69851af2a0f33427df1476100ccb87d409c39e2182837a96b98fb38c4ef2ba6b87bdad62bb70a2c153876b8061760583c languageName: node linkType: hard @@ -9521,12 +9528,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.11.0": - version: 6.11.0 - resolution: "qs@npm:6.11.0" +"qs@npm:6.13.0": + version: 6.13.0 + resolution: "qs@npm:6.13.0" dependencies: - side-channel: ^1.0.4 - checksum: 6e1f29dd5385f7488ec74ac7b6c92f4d09a90408882d0c208414a34dd33badc1a621019d4c799a3df15ab9b1d0292f97c1dd71dc7c045e69f81a8064e5af7297 + side-channel: ^1.0.6 + checksum: e9404dc0fc2849245107108ce9ec2766cde3be1b271de0bf1021d049dc5b98d1a2901e67b431ac5509f865420a7ed80b7acb3980099fe1c118a1c5d2e1432ad8 languageName: node linkType: hard @@ -10377,9 +10384,9 @@ __metadata: languageName: node linkType: hard -"send@npm:0.18.0": - version: 0.18.0 - resolution: "send@npm:0.18.0" +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" dependencies: debug: 2.6.9 depd: 2.0.0 @@ -10394,7 +10401,7 @@ __metadata: on-finished: 2.4.1 range-parser: ~1.2.1 statuses: 2.0.1 - checksum: 74fc07ebb58566b87b078ec63e5a3e41ecd987e4272ba67b7467e86c6ad51bc6b0b0154133b6d8b08a2ddda360464f71382f7ef864700f34844a76c8027817a8 + checksum: 5ae11bd900c1c2575525e2aa622e856804e2f96a09281ec1e39610d089f53aa69e13fd8db84b52f001d0318cf4bb0b3b904ad532fc4c0014eb90d32db0cff55f languageName: node linkType: hard @@ -10438,15 +10445,15 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.15.0": - version: 1.15.0 - resolution: "serve-static@npm:1.15.0" +"serve-static@npm:1.16.2": + version: 1.16.2 + resolution: "serve-static@npm:1.16.2" dependencies: - encodeurl: ~1.0.2 + encodeurl: ~2.0.0 escape-html: ~1.0.3 parseurl: ~1.3.3 - send: 0.18.0 - checksum: af57fc13be40d90a12562e98c0b7855cf6e8bd4c107fe9a45c212bf023058d54a1871b1c89511c3958f70626fff47faeb795f5d83f8cf88514dbaeb2b724464d + send: 0.19.0 + checksum: dffc52feb4cc5c68e66d0c7f3c1824d4e989f71050aefc9bd5f822a42c54c9b814f595fc5f2b717f4c7cc05396145f3e90422af31186a93f76cf15f707019759 languageName: node linkType: hard @@ -10530,7 +10537,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.4": +"side-channel@npm:^1.0.6": version: 1.0.6 resolution: "side-channel@npm:1.0.6" dependencies: diff --git a/package.json b/package.json index 1ebb50f8..ec982b70 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nadena.dev.modular-avatar", "displayName": "Modular Avatar", - "version": "1.10.0-rc.4", + "version": "1.10.4", "unity": "2022.3", "description": "A suite of tools for assembling your avatar out of reusable components", "author": { @@ -16,6 +16,6 @@ }, "vpmDependencies": { "com.vrchat.avatars": ">=3.7.0", - "nadena.dev.ndmf": ">=1.5.0-rc.4 <2.0.0-a" + "nadena.dev.ndmf": ">=1.5.4 <2.0.0-a" } }