2023-01-05 20:30:01 +08:00
|
|
|
|
using System;
|
2023-02-25 15:45:24 +08:00
|
|
|
|
using System.Collections.Generic;
|
2023-07-29 23:35:07 +08:00
|
|
|
|
using System.Linq;
|
2023-01-05 20:30:01 +08:00
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEditor.Animations;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using VRC.SDK3.Avatars.Components;
|
2023-02-25 15:45:24 +08:00
|
|
|
|
using VRC.SDK3.Avatars.ScriptableObjects;
|
2023-01-05 20:30:01 +08:00
|
|
|
|
using Object = UnityEngine.Object;
|
2023-01-05 20:10:22 +08:00
|
|
|
|
|
|
|
|
|
namespace nadena.dev.modular_avatar.core.editor
|
|
|
|
|
{
|
|
|
|
|
internal class BuildContext
|
|
|
|
|
{
|
|
|
|
|
internal readonly VRCAvatarDescriptor AvatarDescriptor;
|
|
|
|
|
internal readonly AnimationDatabase AnimationDatabase = new AnimationDatabase();
|
2023-07-29 23:23:51 +08:00
|
|
|
|
internal readonly UnityEngine.Object AssetContainer;
|
2023-01-05 20:10:22 +08:00
|
|
|
|
|
2023-07-29 23:35:07 +08:00
|
|
|
|
private bool SaveImmediate = false;
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
internal readonly Dictionary<VRCExpressionsMenu, VRCExpressionsMenu> ClonedMenus
|
|
|
|
|
= new Dictionary<VRCExpressionsMenu, VRCExpressionsMenu>();
|
|
|
|
|
|
2023-05-11 20:08:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// This dictionary overrides the _original contents_ of ModularAvatarMenuInstallers. Notably, this does not
|
|
|
|
|
/// replace the source menu for the purposes of identifying any other MAMIs that might install to the same
|
|
|
|
|
/// menu asset.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal readonly Dictionary<ModularAvatarMenuInstaller, Action<VRCExpressionsMenu.Control>> PostProcessControls
|
|
|
|
|
= new Dictionary<ModularAvatarMenuInstaller, Action<VRCExpressionsMenu.Control>>();
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
public BuildContext(VRCAvatarDescriptor avatarDescriptor)
|
|
|
|
|
{
|
|
|
|
|
AvatarDescriptor = avatarDescriptor;
|
2023-01-05 20:30:01 +08:00
|
|
|
|
|
|
|
|
|
// AssetDatabase.CreateAsset is super slow - so only do it once, and add everything else as sub-assets.
|
2023-07-29 23:23:51 +08:00
|
|
|
|
// This scriptable object exists for the sole purpose of providing a placeholder to dump everything we
|
|
|
|
|
// generate into. Note that we use a custom component here to force binary serialization; this saves both
|
|
|
|
|
// time as well as disk space (if you're using manual bake).
|
|
|
|
|
AssetContainer = ScriptableObject.CreateInstance<MAAssetBundle>();
|
2023-01-05 20:30:01 +08:00
|
|
|
|
AssetDatabase.CreateAsset(AssetContainer, Util.GenerateAssetPath());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SaveAsset(Object obj)
|
|
|
|
|
{
|
2023-07-29 23:35:07 +08:00
|
|
|
|
if (!SaveImmediate || AssetDatabase.IsMainAsset(obj) || AssetDatabase.IsSubAsset(obj)) return;
|
2023-01-05 20:30:01 +08:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2023-04-15 15:38:55 +08:00
|
|
|
|
if (controller == null) return null;
|
|
|
|
|
|
2023-07-29 23:35:07 +08:00
|
|
|
|
var merger = new AnimatorCombiner(this, controller.name + " (clone)");
|
2023-01-05 20:30:01 +08:00
|
|
|
|
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)
|
|
|
|
|
{
|
2023-07-29 23:35:07 +08:00
|
|
|
|
var merger = new AnimatorCombiner(this, overrideController.name + " (clone)");
|
2023-01-05 20:30:01 +08:00
|
|
|
|
merger.AddOverrideController("", overrideController, null);
|
|
|
|
|
return merger.Finish();
|
2023-01-05 20:10:22 +08:00
|
|
|
|
}
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
public VRCExpressionsMenu CloneMenu(VRCExpressionsMenu menu)
|
|
|
|
|
{
|
|
|
|
|
if (menu == null) return null;
|
|
|
|
|
if (ClonedMenus.TryGetValue(menu, out var newMenu)) return newMenu;
|
|
|
|
|
newMenu = Object.Instantiate(menu);
|
|
|
|
|
this.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];
|
|
|
|
|
var labelResult = Util.ValidateExpressionMenuIcon(label.icon);
|
|
|
|
|
if (labelResult != Util.ValidateExpressionMenuIconResult.Success)
|
|
|
|
|
{
|
|
|
|
|
label.icon = null;
|
|
|
|
|
control.labels[i] = label;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (control.type == VRCExpressionsMenu.Control.ControlType.SubMenu)
|
|
|
|
|
{
|
|
|
|
|
control.subMenu = CloneMenu(control.subMenu);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return newMenu;
|
|
|
|
|
}
|
2023-07-29 23:35:07 +08:00
|
|
|
|
|
|
|
|
|
public void CommitReferencedAssets()
|
|
|
|
|
{
|
|
|
|
|
HashSet<UnityEngine.Object> referencedAssets = new HashSet<UnityEngine.Object>();
|
|
|
|
|
HashSet<UnityEngine.Object> sceneAssets = new HashSet<UnityEngine.Object>();
|
|
|
|
|
|
|
|
|
|
Walk(AvatarDescriptor.gameObject);
|
|
|
|
|
|
2023-07-31 20:04:27 +08:00
|
|
|
|
referencedAssets.RemoveWhere(sceneAssets.Contains);
|
2023-08-02 21:53:58 +08:00
|
|
|
|
referencedAssets.RemoveWhere(a => a is GameObject || a is Component);
|
2023-07-31 20:04:27 +08:00
|
|
|
|
referencedAssets.RemoveWhere(o => !string.IsNullOrEmpty(AssetDatabase.GetAssetPath(o)));
|
|
|
|
|
|
|
|
|
|
int index = 0;
|
|
|
|
|
|
|
|
|
|
foreach (var asset in referencedAssets)
|
2023-07-29 23:35:07 +08:00
|
|
|
|
{
|
2023-07-31 20:04:27 +08:00
|
|
|
|
if (asset.name == "")
|
|
|
|
|
{
|
|
|
|
|
asset.name = "Asset " + index++;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-29 23:35:07 +08:00
|
|
|
|
AssetDatabase.AddObjectToAsset(asset, AssetContainer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SaveImmediate = true;
|
|
|
|
|
|
2023-08-03 19:13:27 +08:00
|
|
|
|
void Walk(GameObject root)
|
2023-07-29 23:35:07 +08:00
|
|
|
|
{
|
2023-08-03 19:13:27 +08:00
|
|
|
|
var components = AvatarDescriptor.gameObject.GetComponentsInChildren<Component>(true);
|
|
|
|
|
Queue<UnityEngine.Object> visitQueue = new Queue<UnityEngine.Object>(
|
|
|
|
|
components.Where(t => (!(t is Transform)))
|
|
|
|
|
);
|
2023-07-29 23:35:07 +08:00
|
|
|
|
|
2023-08-03 19:13:27 +08:00
|
|
|
|
while (visitQueue.Count > 0)
|
2023-07-29 23:35:07 +08:00
|
|
|
|
{
|
2023-08-03 19:13:27 +08:00
|
|
|
|
var current = visitQueue.Dequeue();
|
|
|
|
|
if (referencedAssets.Contains(current)) continue;
|
|
|
|
|
referencedAssets.Add(current);
|
|
|
|
|
|
|
|
|
|
// These assets have large internal arrays we don't want to walk through...
|
|
|
|
|
if (current is Mesh || current is AnimationClip || current is Texture) continue;
|
|
|
|
|
|
|
|
|
|
var so = new SerializedObject(current);
|
|
|
|
|
var sp = so.GetIterator();
|
|
|
|
|
bool enterChildren = true;
|
|
|
|
|
|
|
|
|
|
while (sp.Next(enterChildren))
|
2023-07-29 23:35:07 +08:00
|
|
|
|
{
|
2023-08-03 19:13:27 +08:00
|
|
|
|
enterChildren = true;
|
|
|
|
|
if (sp.name == "m_GameObject") continue;
|
|
|
|
|
if (sp.propertyType == SerializedPropertyType.String)
|
2023-07-29 23:35:07 +08:00
|
|
|
|
{
|
2023-08-03 19:13:27 +08:00
|
|
|
|
enterChildren = false;
|
|
|
|
|
continue;
|
2023-07-29 23:35:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-03 19:13:27 +08:00
|
|
|
|
if (sp.isArray && IsPrimitiveArray(sp))
|
|
|
|
|
{
|
|
|
|
|
enterChildren = false;
|
|
|
|
|
}
|
2023-07-29 23:35:07 +08:00
|
|
|
|
|
2023-08-03 19:13:27 +08:00
|
|
|
|
if (sp.propertyType != SerializedPropertyType.ObjectReference)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-07-29 23:35:07 +08:00
|
|
|
|
|
2023-08-03 19:13:27 +08:00
|
|
|
|
var obj = sp.objectReferenceValue;
|
|
|
|
|
if (obj != null && !referencedAssets.Contains(obj) && !(obj is Transform) &&
|
|
|
|
|
!(obj is GameObject))
|
2023-07-29 23:35:07 +08:00
|
|
|
|
{
|
2023-08-03 19:13:27 +08:00
|
|
|
|
visitQueue.Enqueue(sp.objectReferenceValue);
|
2023-07-29 23:35:07 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-03 19:13:27 +08:00
|
|
|
|
|
|
|
|
|
private bool IsPrimitiveArray(SerializedProperty prop)
|
|
|
|
|
{
|
|
|
|
|
if (prop.arraySize == 0) return false;
|
|
|
|
|
var propertyType = prop.GetArrayElementAtIndex(0).propertyType;
|
|
|
|
|
switch (propertyType)
|
|
|
|
|
{
|
|
|
|
|
case SerializedPropertyType.Generic:
|
|
|
|
|
case SerializedPropertyType.ObjectReference:
|
|
|
|
|
return false;
|
|
|
|
|
default:
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-05 20:10:22 +08:00
|
|
|
|
}
|
|
|
|
|
}
|