mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-01-01 20:25:07 +08:00
610ce40270
Closes: #549
225 lines
8.1 KiB
C#
225 lines
8.1 KiB
C#
#region
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using nadena.dev.ndmf;
|
|
using UnityEditor;
|
|
using UnityEditor.Animations;
|
|
using UnityEngine;
|
|
|
|
#if MA_VRCSDK3_AVATARS
|
|
using VRC.SDK3.Avatars.Components;
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
namespace nadena.dev.modular_avatar.animation
|
|
{
|
|
internal static class AnimationUtil
|
|
{
|
|
private const string SAMPLE_PATH_PACKAGE =
|
|
"Packages/com.vrchat.avatars/Samples/AV3 Demo Assets/Animation/Controllers";
|
|
|
|
private const string SAMPLE_PATH_LEGACY = "Assets/VRCSDK/Examples3/Animation/Controllers";
|
|
|
|
private const string GUID_GESTURE_HANDSONLY_MASK = "b2b8bad9583e56a46a3e21795e96ad92";
|
|
|
|
|
|
public static AnimatorController DeepCloneAnimator(BuildContext context, RuntimeAnimatorController controller)
|
|
{
|
|
if (controller == null) return null;
|
|
|
|
var merger = new AnimatorCombiner(context, controller.name + " (cloned)");
|
|
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();
|
|
}
|
|
|
|
internal static void CloneAllControllers(BuildContext context)
|
|
{
|
|
// Ensure all of the controllers on the avatar descriptor point to temporary assets.
|
|
// This helps reduce the risk that we'll accidentally modify the original assets.
|
|
|
|
#if MA_VRCSDK3_AVATARS
|
|
context.AvatarDescriptor.baseAnimationLayers =
|
|
CloneLayers(context, context.AvatarDescriptor.baseAnimationLayers);
|
|
context.AvatarDescriptor.specialAnimationLayers =
|
|
CloneLayers(context, context.AvatarDescriptor.specialAnimationLayers);
|
|
#endif
|
|
}
|
|
|
|
#if MA_VRCSDK3_AVATARS
|
|
private static VRCAvatarDescriptor.CustomAnimLayer[] CloneLayers(
|
|
BuildContext context,
|
|
VRCAvatarDescriptor.CustomAnimLayer[] layers
|
|
)
|
|
{
|
|
if (layers == null) return null;
|
|
|
|
for (int i = 0; i < layers.Length; i++)
|
|
{
|
|
var layer = layers[i];
|
|
if (layer.animatorController != null && !context.IsTemporaryAsset(layer.animatorController))
|
|
{
|
|
layer.animatorController = DeepCloneAnimator(context, layer.animatorController);
|
|
}
|
|
|
|
layers[i] = layer;
|
|
}
|
|
|
|
return layers;
|
|
}
|
|
|
|
public static AnimatorController GetOrInitializeController(
|
|
this BuildContext context,
|
|
VRCAvatarDescriptor.AnimLayerType type)
|
|
{
|
|
return FindLayer(context.AvatarDescriptor.baseAnimationLayers)
|
|
?? FindLayer(context.AvatarDescriptor.specialAnimationLayers);
|
|
|
|
AnimatorController FindLayer(VRCAvatarDescriptor.CustomAnimLayer[] layers)
|
|
{
|
|
for (int i = 0; i < layers.Length; i++)
|
|
{
|
|
var layer = layers[i];
|
|
if (layer.type == type)
|
|
{
|
|
if (layer.animatorController == null || layer.isDefault)
|
|
{
|
|
layer.animatorController = ResolveLayerController(layer);
|
|
if (type == VRCAvatarDescriptor.AnimLayerType.Gesture)
|
|
{
|
|
layer.mask = AssetDatabase.LoadAssetAtPath<AvatarMask>(
|
|
AssetDatabase.GUIDToAssetPath(GUID_GESTURE_HANDSONLY_MASK)
|
|
);
|
|
}
|
|
|
|
layers[i] = layer;
|
|
}
|
|
|
|
return layer.animatorController as AnimatorController;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
private static AnimatorController ResolveLayerController(VRCAvatarDescriptor.CustomAnimLayer layer)
|
|
{
|
|
AnimatorController controller = null;
|
|
|
|
if (!layer.isDefault && layer.animatorController != null &&
|
|
layer.animatorController is AnimatorController c)
|
|
{
|
|
controller = c;
|
|
}
|
|
else
|
|
{
|
|
string name;
|
|
switch (layer.type)
|
|
{
|
|
case VRCAvatarDescriptor.AnimLayerType.Action:
|
|
name = "Action";
|
|
break;
|
|
case VRCAvatarDescriptor.AnimLayerType.Additive:
|
|
name = "Idle";
|
|
break;
|
|
case VRCAvatarDescriptor.AnimLayerType.Base:
|
|
name = "Locomotion";
|
|
break;
|
|
case VRCAvatarDescriptor.AnimLayerType.Gesture:
|
|
name = "Hands";
|
|
break;
|
|
case VRCAvatarDescriptor.AnimLayerType.Sitting:
|
|
name = "Sitting";
|
|
break;
|
|
case VRCAvatarDescriptor.AnimLayerType.FX:
|
|
name = "Face";
|
|
break;
|
|
case VRCAvatarDescriptor.AnimLayerType.TPose:
|
|
name = "UtilityTPose";
|
|
break;
|
|
case VRCAvatarDescriptor.AnimLayerType.IKPose:
|
|
name = "UtilityIKPose";
|
|
break;
|
|
default:
|
|
name = null;
|
|
break;
|
|
}
|
|
|
|
if (name != null)
|
|
{
|
|
name = "/vrc_AvatarV3" + name + "Layer.controller";
|
|
|
|
controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(SAMPLE_PATH_PACKAGE + name);
|
|
if (controller == null)
|
|
{
|
|
controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(SAMPLE_PATH_LEGACY + name);
|
|
}
|
|
}
|
|
}
|
|
|
|
return controller;
|
|
}
|
|
#endif
|
|
|
|
public static bool IsProxyAnimation(this Motion m)
|
|
{
|
|
var path = AssetDatabase.GetAssetPath(m);
|
|
|
|
// This is a fairly wide condition in order to deal with:
|
|
// 1. Future additions of proxy animations (so GUIDs are out)
|
|
// 2. Unitypackage based installations of the VRCSDK
|
|
// 3. VCC based installations of the VRCSDK
|
|
// 4. Very old VCC based installations of the VRCSDK where proxy animations were copied into Assets
|
|
return path.Contains("/AV3 Demo Assets/Animation/ProxyAnim/proxy")
|
|
|| path.Contains("/VRCSDK/Examples3/Animation/ProxyAnim/proxy")
|
|
|| path.StartsWith("Packages/com.vrchat.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerates all states in an animator controller
|
|
/// </summary>
|
|
/// <param name="ac"></param>
|
|
/// <returns></returns>
|
|
internal static IEnumerable<AnimatorState> States(AnimatorController ac)
|
|
{
|
|
HashSet<AnimatorStateMachine> visitedStateMachines = new HashSet<AnimatorStateMachine>();
|
|
Queue<AnimatorStateMachine> pending = new Queue<AnimatorStateMachine>();
|
|
|
|
foreach (var layer in ac.layers)
|
|
{
|
|
if (layer.stateMachine != null) pending.Enqueue(layer.stateMachine);
|
|
}
|
|
|
|
while (pending.Count > 0)
|
|
{
|
|
var next = pending.Dequeue();
|
|
if (visitedStateMachines.Contains(next)) continue;
|
|
visitedStateMachines.Add(next);
|
|
|
|
foreach (var child in next.stateMachines)
|
|
{
|
|
if (child.stateMachine != null) pending.Enqueue(child.stateMachine);
|
|
}
|
|
|
|
foreach (var state in next.states)
|
|
{
|
|
yield return state.state;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |