diff --git a/Packages/nadena.dev.modular-avatar/Editor/AnimatorMerger.cs b/Packages/nadena.dev.modular-avatar/Editor/AnimatorMerger.cs index 619071f3..03bb732f 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/AnimatorMerger.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/AnimatorMerger.cs @@ -52,9 +52,9 @@ namespace nadena.dev.modular_avatar.core.editor private int controllerBaseLayer = 0; - public AnimatorCombiner() + public AnimatorCombiner(BuildContext context) { - _combined = Util.CreateAnimator(); + _combined = context.CreateAnimator(); } public AnimatorController Finish() diff --git a/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs b/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs index 042178b5..11b46f46 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs @@ -156,16 +156,16 @@ namespace nadena.dev.modular_avatar.core.editor var context = new BuildContext(vrcAvatarDescriptor); - new RenameParametersHook().OnPreprocessAvatar(avatarGameObject); - new MergeAnimatorProcessor().OnPreprocessAvatar(avatarGameObject); + new RenameParametersHook().OnPreprocessAvatar(avatarGameObject, context); + new MergeAnimatorProcessor().OnPreprocessAvatar(avatarGameObject, context); context.AnimationDatabase.Bootstrap(vrcAvatarDescriptor); - new MenuInstallHook().OnPreprocessAvatar(avatarGameObject); + new MenuInstallHook().OnPreprocessAvatar(avatarGameObject, context); new MergeArmatureHook().OnPreprocessAvatar(context, avatarGameObject); new BoneProxyProcessor().OnPreprocessAvatar(avatarGameObject); new VisibleHeadAccessoryProcessor(vrcAvatarDescriptor).Process(); new RemapAnimationPass(vrcAvatarDescriptor).Process(context.AnimationDatabase); - new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(avatarGameObject, context.AnimationDatabase); + new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(avatarGameObject, context); PhysboneBlockerPass.Process(avatarGameObject); context.AnimationDatabase.Commit(); diff --git a/Packages/nadena.dev.modular-avatar/Editor/BlendshapeSyncAnimationProcessor.cs b/Packages/nadena.dev.modular-avatar/Editor/BlendshapeSyncAnimationProcessor.cs index e23c50c3..60ccbbc5 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/BlendshapeSyncAnimationProcessor.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/BlendshapeSyncAnimationProcessor.cs @@ -16,7 +16,7 @@ namespace nadena.dev.modular_avatar.core.editor */ internal class BlendshapeSyncAnimationProcessor { - private Object _container; + private BuildContext _context; private Dictionary _motionCache; private Dictionary> _bindingMappings; @@ -43,8 +43,11 @@ namespace nadena.dev.modular_avatar.core.editor } } - public void OnPreprocessAvatar(GameObject avatar, AnimationDatabase animDb) + public void OnPreprocessAvatar(GameObject avatar, BuildContext context) { + _context = context; + var animDb = _context.AnimationDatabase; + var avatarDescriptor = avatar.GetComponent(); _bindingMappings = new Dictionary>(); _motionCache = new Dictionary(); @@ -145,15 +148,7 @@ namespace nadena.dev.modular_avatar.core.editor { var newTree = new BlendTree(); EditorUtility.CopySerialized(tree, newTree); - if (_container == null) - { - _container = newTree; - AssetDatabase.CreateAsset(_container, Util.GenerateAssetPath()); - } - else - { - AssetDatabase.AddObjectToAsset(newTree, _container); - } + _context.SaveAsset(newTree); newTree.children = children; motion = newTree; diff --git a/Packages/nadena.dev.modular-avatar/Editor/BuildContext.cs b/Packages/nadena.dev.modular-avatar/Editor/BuildContext.cs index d515972d..b3c46283 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/BuildContext.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/BuildContext.cs @@ -1,4 +1,9 @@ -using VRC.SDK3.Avatars.Components; +using System; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using Object = UnityEngine.Object; namespace nadena.dev.modular_avatar.core.editor { @@ -6,10 +11,66 @@ namespace nadena.dev.modular_avatar.core.editor { internal readonly VRCAvatarDescriptor AvatarDescriptor; internal readonly AnimationDatabase AnimationDatabase = new AnimationDatabase(); + internal readonly AnimatorController AssetContainer; public BuildContext(VRCAvatarDescriptor avatarDescriptor) { AvatarDescriptor = avatarDescriptor; + + // AssetDatabase.CreateAsset is super slow - so only do it once, and add everything else as sub-assets. + // This animator controller exists for the sole purpose of providing a placeholder to dump everything we + // generate into. + AssetContainer = new AnimatorController(); + AssetDatabase.CreateAsset(AssetContainer, Util.GenerateAssetPath()); + } + + public void SaveAsset(Object obj) + { + if (AssetDatabase.IsMainAsset(obj) || AssetDatabase.IsSubAsset(obj)) return; + + AssetDatabase.AddObjectToAsset(obj, AssetContainer); + } + + public AnimatorController CreateAnimator(AnimatorController toClone = null) + { + AnimatorController controller; + if (toClone != null) + { + controller = Object.Instantiate(toClone); + } + else + { + controller = new AnimatorController(); + } + + SaveAsset(controller); + + return controller; + } + + public AnimatorController DeepCloneAnimator(RuntimeAnimatorController controller) + { + var merger = new AnimatorCombiner(this); + switch (controller) + { + case AnimatorController ac: + merger.AddController("", ac, null); + break; + case AnimatorOverrideController oac: + merger.AddOverrideController("", oac, null); + break; + default: + throw new Exception("Unknown RuntimeAnimatorContoller type " + controller.GetType()); + } + + return merger.Finish(); + } + + public AnimatorController ConvertAnimatorController(AnimatorOverrideController overrideController) + { + var merger = new AnimatorCombiner(this); + merger.AddOverrideController("", overrideController, null); + return merger.Finish(); } } } \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs b/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs index c469fd2e..1f09d0aa 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using UnityEditor; using UnityEngine; using VRC.SDK3.Avatars.Components; @@ -16,31 +17,36 @@ namespace nadena.dev.modular_avatar.core.editor "Packages/nadena.dev.modular-avatar/Runtime/Icons/Icon_More_A.png" ); + private BuildContext _context; + private Dictionary _clonedMenus; - + private VRCExpressionsMenu _rootMenu; private MenuTree _menuTree; private Stack _visitedInstallerStack; - - public void OnPreprocessAvatar(GameObject avatarRoot) + + public void OnPreprocessAvatar(GameObject avatarRoot, BuildContext context) { - ModularAvatarMenuInstaller[] menuInstallers = avatarRoot.GetComponentsInChildren(true) + _context = context; + + ModularAvatarMenuInstaller[] menuInstallers = avatarRoot + .GetComponentsInChildren(true) .Where(menuInstaller => menuInstaller.enabled) .ToArray(); if (menuInstallers.Length == 0) return; - + _clonedMenus = new Dictionary(); _visitedInstallerStack = new Stack(); - + VRCAvatarDescriptor avatar = avatarRoot.GetComponent(); - if (avatar.expressionsMenu == null) + if (avatar.expressionsMenu == null) { var menu = ScriptableObject.CreateInstance(); - AssetDatabase.CreateAsset(menu, Util.GenerateAssetPath()); + _context.SaveAsset(menu); avatar.expressionsMenu = menu; _clonedMenus[menu] = menu; } @@ -48,20 +54,20 @@ namespace nadena.dev.modular_avatar.core.editor _rootMenu = avatar.expressionsMenu; _menuTree = new MenuTree(avatar); _menuTree.TraverseAvatarMenu(); - + avatar.expressionsMenu = CloneMenu(avatar.expressionsMenu); - - foreach (ModularAvatarMenuInstaller installer in menuInstallers) + + foreach (ModularAvatarMenuInstaller installer in menuInstallers) { _menuTree.TraverseMenuInstaller(installer); } - - foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(null)) + + foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(null)) { InstallMenu(childElement.installer); } } - + private void InstallMenu(ModularAvatarMenuInstaller installer, VRCExpressionsMenu installTarget = null) { if (!installer.enabled) return; @@ -71,22 +77,22 @@ namespace nadena.dev.modular_avatar.core.editor installer.installTargetMenu = _rootMenu; } - if (installTarget == null) + if (installTarget == null) { installTarget = installer.installTargetMenu; } if (installer.installTargetMenu == null || installer.menuToAppend == null) return; if (!_clonedMenus.TryGetValue(installTarget, out var targetMenu)) return; - + // Clone before appending to sanitize menu icons targetMenu.controls.AddRange(CloneMenu(installer.menuToAppend).controls); SplitMenu(installer, targetMenu); - + if (_visitedInstallerStack.Contains(installer)) return; _visitedInstallerStack.Push(installer); - foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(installer)) + foreach (MenuTree.ChildElement childElement in _menuTree.GetChildInstallers(installer)) { InstallMenu(childElement.installer, childElement.parent); } @@ -94,13 +100,13 @@ namespace nadena.dev.modular_avatar.core.editor _visitedInstallerStack.Pop(); } - private void SplitMenu(ModularAvatarMenuInstaller installer, VRCExpressionsMenu targetMenu) + private void SplitMenu(ModularAvatarMenuInstaller installer, VRCExpressionsMenu targetMenu) { - while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS) + while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS) { // Split target menu var newMenu = ScriptableObject.CreateInstance(); - AssetDatabase.CreateAsset(newMenu, Util.GenerateAssetPath()); + _context.SaveAsset(newMenu); const int keepCount = VRCExpressionsMenu.MAX_CONTROLS - 1; newMenu.controls.AddRange(targetMenu.controls.Skip(keepCount)); targetMenu.controls.RemoveRange(keepCount, @@ -125,20 +131,20 @@ namespace nadena.dev.modular_avatar.core.editor targetMenu = newMenu; } } - + private VRCExpressionsMenu CloneMenu(VRCExpressionsMenu menu) { if (menu == null) return null; if (_clonedMenus.TryGetValue(menu, out var newMenu)) return newMenu; newMenu = Object.Instantiate(menu); - AssetDatabase.CreateAsset(newMenu, Util.GenerateAssetPath()); + _context.SaveAsset(newMenu); _clonedMenus[menu] = newMenu; - + foreach (var control in newMenu.controls) { if (Util.ValidateExpressionMenuIcon(control.icon) != Util.ValidateExpressionMenuIconResult.Success) control.icon = null; - + for (int i = 0; i < control.labels.Length; i++) { var label = control.labels[i]; @@ -149,7 +155,7 @@ namespace nadena.dev.modular_avatar.core.editor control.labels[i] = label; } } - + if (control.type == VRCExpressionsMenu.Control.ControlType.SubMenu) { control.subMenu = CloneMenu(control.subMenu); diff --git a/Packages/nadena.dev.modular-avatar/Editor/MergeAnimatorProcessor.cs b/Packages/nadena.dev.modular-avatar/Editor/MergeAnimatorProcessor.cs index f02d5ccd..a8e18bba 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/MergeAnimatorProcessor.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/MergeAnimatorProcessor.cs @@ -39,6 +39,8 @@ namespace nadena.dev.modular_avatar.core.editor private const string SAMPLE_PATH_LEGACY = "Assets/VRCSDK/Examples3/Animation/Controllers"; + private BuildContext _context; + private Dictionary defaultControllers_ = new Dictionary(); @@ -48,8 +50,10 @@ namespace nadena.dev.modular_avatar.core.editor Dictionary mergeSessions = new Dictionary(); - internal void OnPreprocessAvatar(GameObject avatarGameObject) + internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext context) { + _context = context; + defaultControllers_.Clear(); mergeSessions.Clear(); @@ -77,7 +81,7 @@ namespace nadena.dev.modular_avatar.core.editor if (!mergeSessions.TryGetValue(merge.layerType, out var session)) { - session = new AnimatorCombiner(); + session = new AnimatorCombiner(context); mergeSessions[merge.layerType] = session; if (defaultControllers_.ContainsKey(merge.layerType)) { @@ -131,7 +135,7 @@ namespace nadena.dev.modular_avatar.core.editor { // For non-default layers, ensure we always clone the controller for the benefit of subsequent // processing phases - mergeSessions[layer.type] = new AnimatorCombiner(); + mergeSessions[layer.type] = new AnimatorCombiner(_context); mergeSessions[layer.type].AddController("", controller, null); } } diff --git a/Packages/nadena.dev.modular-avatar/Editor/MergeArmatureHook.cs b/Packages/nadena.dev.modular-avatar/Editor/MergeArmatureHook.cs index 12a1d351..a92c0417 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/MergeArmatureHook.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/MergeArmatureHook.cs @@ -80,7 +80,7 @@ namespace nadena.dev.modular_avatar.core.editor RetainBoneReferences(c as Component); } - new RetargetMeshes().OnPreprocessAvatar(avatarGameObject); + new RetargetMeshes().OnPreprocessAvatar(avatarGameObject, context); } private void RetainBoneReferences(Component c) diff --git a/Packages/nadena.dev.modular-avatar/Editor/MeshRetargeter.cs b/Packages/nadena.dev.modular-avatar/Editor/MeshRetargeter.cs index 805bf6bf..bd2ca750 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/MeshRetargeter.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/MeshRetargeter.cs @@ -77,8 +77,12 @@ namespace nadena.dev.modular_avatar.core.editor internal class RetargetMeshes { - internal void OnPreprocessAvatar(GameObject avatarGameObject) + private BuildContext _context; + + internal void OnPreprocessAvatar(GameObject avatarGameObject, BuildContext context) { + _context = context; + foreach (var renderer in avatarGameObject.GetComponentsInChildren(true)) { bool isRetargetable = false; @@ -93,7 +97,8 @@ namespace nadena.dev.modular_avatar.core.editor if (isRetargetable) { - new MeshRetargeter(renderer).Retarget(); + var newMesh = new MeshRetargeter(renderer).Retarget(); + _context.SaveAsset(newMesh); } } @@ -139,7 +144,7 @@ namespace nadena.dev.modular_avatar.core.editor this.renderer = renderer; } - public void Retarget() + public Mesh Retarget() { var avatar = RuntimeUtil.FindAvatarInParents(renderer.transform); if (avatar == null) throw new System.Exception("Could not find avatar in parents of " + renderer.name); @@ -164,7 +169,7 @@ namespace nadena.dev.modular_avatar.core.editor avatarTransform.rotation = avRot; avatarTransform.localScale = avScale; - AssetDatabase.CreateAsset(dst, Util.GenerateAssetPath()); + return dst; } private void AdjustShapeKeys() diff --git a/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs b/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs index b4fba0e9..bb6f55f5 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs @@ -17,13 +17,17 @@ namespace nadena.dev.modular_avatar.core.editor { private const string DEFAULT_EXP_PARAMS_ASSET_GUID = "03a6d797deb62f0429471c4e17ea99a7"; + private BuildContext _context; + private int internalParamIndex = 0; private Dictionary _syncedParams = new Dictionary(); - public void OnPreprocessAvatar(GameObject avatar) + public void OnPreprocessAvatar(GameObject avatar, BuildContext context) { + _context = context; + _syncedParams.Clear(); WalkTree(avatar, ImmutableDictionary.Empty, ImmutableDictionary.Empty); @@ -49,7 +53,7 @@ namespace nadena.dev.modular_avatar.core.editor } expParams = Object.Instantiate(expParams); - AssetDatabase.CreateAsset(expParams, Util.GenerateAssetPath()); + _context.SaveAsset(expParams); var knownParams = expParams.parameters.Select(p => p.name).ToImmutableHashSet(); var parameters = expParams.parameters.ToList(); @@ -129,7 +133,7 @@ namespace nadena.dev.modular_avatar.core.editor // RuntimeAnimatorController may be AnimatorOverrideController, convert in case of AnimatorOverrideController if (anim.runtimeAnimatorController is AnimatorOverrideController overrideController) { - anim.runtimeAnimatorController = Util.ConvertAnimatorController(overrideController); + anim.runtimeAnimatorController = _context.ConvertAnimatorController(overrideController); } var controller = anim.runtimeAnimatorController as AnimatorController; @@ -147,7 +151,7 @@ namespace nadena.dev.modular_avatar.core.editor // RuntimeAnimatorController may be AnimatorOverrideController, convert in case of AnimatorOverrideController if (merger.animator is AnimatorOverrideController overrideController) { - merger.animator = Util.ConvertAnimatorController(overrideController); + merger.animator = _context.ConvertAnimatorController(overrideController); } var controller = merger.animator as AnimatorController; @@ -192,7 +196,7 @@ namespace nadena.dev.modular_avatar.core.editor if (remapped.TryGetValue(menu, out var newMenu)) return newMenu; newMenu = Object.Instantiate(menu); - AssetDatabase.CreateAsset(newMenu, Util.GenerateAssetPath()); + _context.SaveAsset(newMenu); remapped[menu] = newMenu; ClonedMenuMappings.Add(menu, newMenu); @@ -222,7 +226,7 @@ namespace nadena.dev.modular_avatar.core.editor // Deep clone the animator if (!Util.IsTemporaryAsset(controller)) { - controller = Util.DeepCloneAnimator(controller); + controller = _context.DeepCloneAnimator(controller); } var parameters = controller.parameters; diff --git a/Packages/nadena.dev.modular-avatar/Editor/Util.cs b/Packages/nadena.dev.modular-avatar/Editor/Util.cs index 11de9be4..b97c50f9 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Util.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Util.cs @@ -60,23 +60,6 @@ namespace nadena.dev.modular_avatar.core.editor EditorApplication.hierarchyChanged += () => { RuntimeUtil.InvokeHierarchyChanged(); }; } - public static AnimatorController CreateAnimator(AnimatorController toClone = null) - { - AnimatorController controller; - if (toClone != null) - { - controller = Object.Instantiate(toClone); - } - else - { - controller = new AnimatorController(); - } - - AssetDatabase.CreateAsset(controller, GenerateAssetPath()); - - return controller; - } - public static string GenerateAssetPath() { return GetGeneratedAssetsFolder() + "/" + GUID.Generate() + ".asset"; @@ -113,31 +96,6 @@ namespace nadena.dev.modular_avatar.core.editor }; } - public static AnimatorController DeepCloneAnimator(RuntimeAnimatorController controller) - { - var merger = new AnimatorCombiner(); - switch (controller) - { - case AnimatorController ac: - merger.AddController("", ac, null); - break; - case AnimatorOverrideController oac: - merger.AddOverrideController("", oac, null); - break; - default: - throw new Exception("Unknown RuntimeAnimatorContoller type " + controller.GetType()); - } - - return merger.Finish(); - } - - public static AnimatorController ConvertAnimatorController(AnimatorOverrideController overrideController) - { - var merger = new AnimatorCombiner(); - merger.AddOverrideController("", overrideController, null); - return merger.Finish(); - } - public static bool IsTemporaryAsset(Object obj) { var path = AssetDatabase.GetAssetPath(obj);