modular-avatar/Packages/nadena.dev.modular-avatar/Editor/FixupPasses/FixupExpressionsMenuPass.cs
bd_ c454bc1ed8
fix: blendshape sync not being processed (#466)
* Add integration test for blendshape sync

* fix: blendshape sync not being processed

This change refactors AnimationDatabase to be part of the same extension
context as the TrackObjectRenames functionality (which is renamed back to
PathMappings). This then allows us to sequence deactivation of this context
to come after blendshape processing completes.

Fixes: #461
2023-10-01 00:09:43 +09:00

160 lines
6.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using VRC.SDK3.Avatars.ScriptableObjects;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core.editor
{
internal class FixupExpressionsMenuPass
{
private const string DEFAULT_EXP_MENU_GUID = "024fb8ef5b3988c46b446863c92f4522";
private const string DEFAULT_EXP_PARAM_GUID = "03a6d797deb62f0429471c4e17ea99a7";
internal static void FixupExpressionsMenu(BuildContext context)
{
context.AvatarDescriptor.customExpressions = true;
var expressionsMenu = context.AvatarDescriptor.expressionsMenu;
if (expressionsMenu == null)
{
var defaultExpMenu = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>(
AssetDatabase.GUIDToAssetPath(DEFAULT_EXP_MENU_GUID)
);
expressionsMenu = Object.Instantiate(defaultExpMenu);
context.AvatarDescriptor.expressionsMenu = expressionsMenu;
}
if (context.AvatarDescriptor.expressionParameters == null)
{
var defaultExpParam = AssetDatabase.LoadAssetAtPath<VRCExpressionParameters>(
AssetDatabase.GUIDToAssetPath(DEFAULT_EXP_PARAM_GUID)
);
context.AvatarDescriptor.expressionParameters = Object.Instantiate(defaultExpParam);
}
var parameters = context.AvatarDescriptor.expressionParameters.parameters
?? new VRCExpressionParameters.Parameter[0];
var parameterNames = parameters.Select(p => p.name).ToImmutableHashSet();
if (!context.PluginBuildContext.IsTemporaryAsset(expressionsMenu))
{
expressionsMenu = context.CloneMenu(expressionsMenu);
context.AvatarDescriptor.expressionsMenu = expressionsMenu;
}
// Walk menu recursively
var visitedMenus = new HashSet<VRCExpressionsMenu>();
var iconMapping = new Dictionary<Texture2D, Texture2D>();
VisitMenu(expressionsMenu);
void VisitMenu(VRCExpressionsMenu menu)
{
if (!visitedMenus.Add(menu)) return;
foreach (var control in menu.controls)
{
if (control.parameter != null &&
!string.IsNullOrEmpty(control.parameter.name) &&
!parameterNames.Contains(control.parameter.name))
{
control.parameter.name = "";
}
foreach (var subParam in control.subParameters ??
Array.Empty<VRCExpressionsMenu.Control.Parameter>())
{
if (subParam != null &&
!string.IsNullOrEmpty(subParam.name) &&
!parameterNames.Contains(subParam.name))
{
subParam.name = "";
}
}
if (control.icon != null)
{
if (!iconMapping.TryGetValue(control.icon, out var newIcon))
{
iconMapping[control.icon] = newIcon = MaybeScaleIcon(context, control.icon);
}
control.icon = newIcon;
}
if (control.labels != null)
{
for (int i = 0; i < control.labels.Length; i++)
{
var label = control.labels[i];
if (label.icon != null)
{
if (!iconMapping.TryGetValue(label.icon, out var newIcon))
{
iconMapping[label.icon] = newIcon = MaybeScaleIcon(context, label.icon);
}
label.icon = newIcon;
control.labels[i] = label;
}
}
}
}
}
}
#if UNITY_ANDROID
private const TextureFormat TargetFormat = TextureFormat.ASTC_4x4;
#else
private const TextureFormat TargetFormat = TextureFormat.DXT5;
#endif
private static Texture2D MaybeScaleIcon(BuildContext context, Texture2D original)
{
if (original.width <= 256 && original.height <= 256 && IsCompressedFormat(original.format))
{
return original;
}
var newRatio = Math.Min(256f / original.width, 256f / original.height);
var newWidth = Math.Min(256, Mathf.RoundToInt(original.width * newRatio));
var newHeight = Math.Min(256, Mathf.RoundToInt(original.height * newRatio));
var newTex = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, true);
context.SaveAsset(newTex);
var tmpRenderTex = RenderTexture.GetTemporary(newWidth, newHeight, 0, RenderTextureFormat.ARGB32);
var originalActiveRenderTex = RenderTexture.active;
try
{
Graphics.Blit(original, tmpRenderTex);
RenderTexture.active = tmpRenderTex;
newTex.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
newTex.Apply();
EditorUtility.CompressTexture(newTex, TargetFormat, TextureCompressionQuality.Normal);
return newTex;
}
finally
{
RenderTexture.active = originalActiveRenderTex;
RenderTexture.ReleaseTemporary(tmpRenderTex);
}
}
private static bool IsCompressedFormat(TextureFormat format)
{
var name = format.ToString();
return name.StartsWith("DXT") || name.StartsWith("ASTC");
}
}
}