From 6574901e8104dfeff212098b6e583a2a2ff99c23 Mon Sep 17 00:00:00 2001 From: yum Date: Wed, 19 Feb 2025 14:46:36 -0800 Subject: formatting --- README.md | 13 +- Scripts/YOTSCore.cs | 484 ++++++++++++++++--------------------------- Scripts/YOTSNDMFConfig.cs | 6 +- Scripts/YOTSNDMFGenerator.cs | 33 +-- 4 files changed, 199 insertions(+), 337 deletions(-) diff --git a/README.md b/README.md index 94cf27f..0c4768b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ Open your text editor of choice and paste this in: { "name": "Shirt", "meshToggles": ["Shirt"], - "blendShapes": [] } ] } @@ -37,7 +36,7 @@ Feel free to replace "Shirt" with the name of some other mesh on your avatar. Save it to Assets/animator.json. Drag Assets/yum\_food/YOTS.prefab anywhere on your avatar. Select it in the -hierarchy, and drag Assets/animator.json onto the "JSON config" field. +hierarchy, and drag Assets/animator.json onto the "Json Config" field. Enter play mode. Enable an emulator (I use Lyuma's av3emulator). Open your menu. You should see a YOTS submenu. Click it, then click Shirt. Your shirt @@ -52,13 +51,14 @@ A logical sequence of things to try: 3. Declare a dependency on another toggle with `"dependencies": ["someOtherToggle"]`. 4. Install a toggle at a custom path with `"menuPath": "/my/custom/path"`. 5. Add a radial puppet with `"type": "radial"`. +6. Use `"defaultValue": 0.0` to set a toggle to off by default. Toggle options are documented in two places: -1. Read the ToggleSpec definition at the top of +1. The ToggleSpec definition at the top of [YOTSCore.cs](./Scripts/YOTSCore.cs). This is the definitive source of truth. -2. Skim the examples under Examples/ +2. The [Examples](./Examples). ## Motivation @@ -87,7 +87,7 @@ the lifetime of an avatar: 1. Adding new articles of clothing. You now have to edit all your existing avatar-wide animations to include them. -2. Adding new kinds of toggles or sliders. You may want them to affect a large +2. Adding new toggles or sliders. You may want them to affect a large set of items. For example: you added a minimum brightness slider, and now have to animate 20 different articles of clothing. 3. Removing articles of clothing. You should remove them from your avatar-wide @@ -95,8 +95,7 @@ the lifetime of an avatar: 4. Removing toggles or sliders. It's easy to accidentally orphan an animator layer, or a parameter somewhere. -These are all vastly easier to perform through a textual configuration system -than through VRChat's native GUI approach. +These are all vastly easier to perform through YOTS. ## Design derivation diff --git a/Scripts/YOTSCore.cs b/Scripts/YOTSCore.cs index 5ee3752..3cfb699 100644 --- a/Scripts/YOTSCore.cs +++ b/Scripts/YOTSCore.cs @@ -13,13 +13,12 @@ using VRC.SDK3.Avatars.ScriptableObjects; namespace YOTS { [System.Serializable] - public class ToggleSpec - { + public class ToggleSpec { // The name of the toggle. This is plumbed into the menu, the VRChat // parameters, and the animator parameters. [SerializeField] public string name; - + // The type of toggle. // Accepted values: // "toggle" - A boolean toggle. Creates a boolean sync param. @@ -54,35 +53,30 @@ namespace YOTS [SerializeField] public float defaultValue = 1.0f; - public ToggleSpec(string name) - { + public ToggleSpec(string name) { this.name = name; } public ToggleSpec() {} - public IEnumerable GetAffectedAttributes() - { - foreach (var mesh in meshToggles) - { + public IEnumerable GetAffectedAttributes() { + foreach (var mesh in meshToggles) { yield return $"MeshToggle:{mesh}"; } - foreach (var blend in blendShapes) - { + foreach (var blend in blendShapes) { yield return $"BlendShape:{blend.path}/{blend.blendShape}"; } } } [System.Serializable] - public class BlendShapeSpec - { + public class BlendShapeSpec { // The path to the mesh renderer to apply the blend shape to. // For example, "Body" or "Shirt". [SerializeField] public string path; - + // The name of the blend shape to apply. // For example, "Chest_Hide" or "Boobs+". [SerializeField] @@ -96,8 +90,7 @@ namespace YOTS [SerializeField] public float onValue = 100.0f; - public BlendShapeSpec(string path, string blendShape, float offValue = 0, float onValue = 100) - { + public BlendShapeSpec(string path, string blendShape, float offValue = 0, float onValue = 100) { this.path = path; this.blendShape = blendShape; this.offValue = offValue; @@ -108,8 +101,7 @@ namespace YOTS } [System.Serializable] - public class AnimatorConfigFile - { + public class AnimatorConfigFile { [SerializeField] public List toggles = new List(); @@ -118,15 +110,13 @@ namespace YOTS } [System.Serializable] - public class GeneratedAnimationsConfig - { + public class GeneratedAnimationsConfig { public List animations = new List(); } [System.Serializable] - public class GeneratedAnimationClipConfig - { + public class GeneratedAnimationClipConfig { public string name; public List meshToggles = new List(); @@ -135,15 +125,13 @@ namespace YOTS } [System.Serializable] - public class GeneratedMeshToggle - { + public class GeneratedMeshToggle { public string path; public float value; } [System.Serializable] - public class GeneratedBlendShape - { + public class GeneratedBlendShape { public string path; public string blendShape; public float value; @@ -151,8 +139,7 @@ namespace YOTS // These classes describe the generated JSON output for the animator configuration. [System.Serializable] - public class GeneratedAnimatorConfig - { + public class GeneratedAnimatorConfig { public List parameters = new List(); public List layers = new List(); public List animations = @@ -160,117 +147,94 @@ namespace YOTS } [System.Serializable] - public class AnimatorLayer - { + public class AnimatorLayer { public string name; public AnimatorDirectBlendTree directBlendTree = new AnimatorDirectBlendTree(); } [System.Serializable] - public class AnimatorDirectBlendTree - { + public class AnimatorDirectBlendTree { public List entries = new List(); } [System.Serializable] - public class AnimatorDirectBlendTreeEntry - { + public class AnimatorDirectBlendTreeEntry { public string name; // animation name public string parameter; // parameter driving the animation } // Add these new classes at the namespace level [System.Serializable] - public class VRCMenuConfig - { + public class VRCMenuConfig { public string menuName = "YOTS"; public List items = new List(); } [System.Serializable] - public class VRCMenuItemConfig - { + public class VRCMenuItemConfig { public string name; public string parameter; public Texture2D icon; } [System.Serializable] - public class AnimatorParameterSetting - { + public class AnimatorParameterSetting { public string name; public float defaultValue; } - public class YOTSCore - { - private static Dictionary animationClipCache = new Dictionary(); + public class YOTSCore { + private static Dictionary animationClips = new Dictionary(); public static AnimatorController GenerateAnimator( string configJson, VRCExpressionParameters vrcParams, VRCExpressionsMenu vrcMenu - ) - { + ) { Debug.Log("=== Starting Animator Generation Process ==="); - if (string.IsNullOrEmpty(configJson)) - { + if (string.IsNullOrEmpty(configJson)) { throw new ArgumentException("No config JSON provided."); } - Debug.Log("Parsing JSON configuration"); AnimatorConfigFile config; - try - { + try { config = JsonUtility.FromJson(configJson); } - catch (System.Exception e) - { + catch (System.Exception e) { throw new ArgumentException($"JSON parsing failed: {e.Message}"); } - if (config == null) - { + if (config == null) { throw new ArgumentException("JSON config is empty or invalid"); } - - if (config.toggles == null) - { + + if (config.toggles == null) { throw new ArgumentException("No toggleSpecs found in configuration"); } Debug.Log($"Configuration loaded. Found {config.toggles.Count} toggles."); - + // Create abstract representation of the animator. GeneratedAnimatorConfig genAnimatorConfig = GenerateNaiveAnimatorConfig(config.toggles); genAnimatorConfig = ApplyIndependentFixToAnimatorConfig(genAnimatorConfig); genAnimatorConfig = RemoveOffAnimationsFromOverrideLayers(genAnimatorConfig); genAnimatorConfig = RemoveUnusedAnimations(genAnimatorConfig); - - // Generate VRChat parameters and menu + // Create actual assets. GenerateVRChatAssets(config.toggles, vrcParams, vrcMenu); - - // Create the animation clips directly from the animator config - // TODO animations should not be persisted to disk unless requested for debuggability CreateAnimationClips(new GeneratedAnimationsConfig { animations = genAnimatorConfig.animations }); - - // Generate and return the animator controller AnimatorController controller = GenerateAnimatorController(genAnimatorConfig); Debug.Log("=== Animator Generation Process Complete ==="); return controller; } - private static void CreateAnimationClips(GeneratedAnimationsConfig animationsConfig) - { - foreach (var clipConfig in animationsConfig.animations) - { + private static void CreateAnimationClips(GeneratedAnimationsConfig animationsConfig) { + foreach (var clipConfig in animationsConfig.animations) { AnimationClip newClip = new AnimationClip(); newClip.name = clipConfig.name; // Apply mesh toggles - foreach (var meshToggle in clipConfig.meshToggles) - { + foreach (var meshToggle in clipConfig.meshToggles) { AnimationCurve curve = new AnimationCurve(new Keyframe(0, meshToggle.value)); EditorCurveBinding binding = new EditorCurveBinding(); binding.path = meshToggle.path; @@ -280,8 +244,7 @@ namespace YOTS } // Apply blend shapes - foreach (var blendShape in clipConfig.blendShapes) - { + foreach (var blendShape in clipConfig.blendShapes) { AnimationCurve curve = AnimationCurve.Constant(0, 0, blendShape.value); EditorCurveBinding binding = new EditorCurveBinding(); binding.path = blendShape.path; @@ -291,13 +254,12 @@ namespace YOTS } // Store in memory cache - animationClipCache[clipConfig.name] = newClip; - Debug.Log("Created animation clip in memory: " + clipConfig.name); + animationClips[clipConfig.name] = newClip; + Debug.Log("Created animation clip " + clipConfig.name); } } - private static AnimatorController GenerateAnimatorController(GeneratedAnimatorConfig animatorConfig) - { + private static AnimatorController GenerateAnimatorController(GeneratedAnimatorConfig animatorConfig) { AnimatorController controller = new AnimatorController(); // Add weight parameter used to ensure that the blendtrees always // run. All layers use this. Documented on vrc.school: @@ -309,8 +271,7 @@ namespace YOTS yots_weight.defaultFloat = 1.0f; parameters_list.Add(yots_weight); // Add all other parameters - foreach (var param in animatorConfig.parameters) - { + foreach (var param in animatorConfig.parameters) { var p = new AnimatorControllerParameter(); p.name = param.name; p.type = AnimatorControllerParameterType.Float; @@ -319,70 +280,72 @@ namespace YOTS } controller.parameters = parameters_list.ToArray(); - // Add base layer - var baseLayer = animatorConfig.layers[0]; + // Add base layer. This is structured as a wide direct blendtree + // (DBT) comprised of blendtrees animating pairs of On/Off + // animations. + var baseLayerConfig = animatorConfig.layers[0]; var baseStateMachine = new AnimatorStateMachine(); - baseStateMachine.name = "BaseLayer_SM"; + baseStateMachine.name = "YOTS_BaseLayer_SM"; var rootBlendTree = new BlendTree(); - rootBlendTree.name = "BaseLayer_RootBlendTree"; + rootBlendTree.name = "YOTS_BaseLayer_RootBlendTree"; rootBlendTree.blendType = BlendTreeType.Direct; - var parameterGroups = baseLayer.directBlendTree.entries + var parameterGroups = baseLayerConfig.directBlendTree.entries .GroupBy(e => e.parameter) .ToDictionary(g => g.Key, g => g.ToList()); - foreach (var group in parameterGroups) - { + // Iterate over (parameter, animationSet) pairs in the base layer. + foreach (var group in parameterGroups) { var param = group.Key; - var entries = group.Value; + var animations = group.Value; + // Create a blendtree controlled by this toggle's parameter. var paramBlendTree = new BlendTree(); - paramBlendTree.name = $"BlendTree_{param}"; + paramBlendTree.name = $"YOTS_BlendTree_{param}"; paramBlendTree.blendType = BlendTreeType.Simple1D; paramBlendTree.blendParameter = param; var children = new List(); - foreach (var entry in entries.OrderBy(e => e.name.EndsWith("_On"))) - { - Debug.Log("Adding child motion for: " + entry.name); - if (!animationClipCache.TryGetValue(entry.name, out AnimationClip clip)) - { - throw new InvalidOperationException($"Animation clip not found in memory: {entry.name}"); + foreach (var animation in animations.OrderBy(e => e.name.EndsWith("_On"))) { + Debug.Log("Adding child motion for: " + animation.name); + if (!animationClips.TryGetValue(animation.name, out AnimationClip clip)) { + throw new InvalidOperationException($"Animation clip not found in memory: {animation.name}"); } - children.Add(new ChildMotion - { + children.Add(new ChildMotion{ motion = clip, timeScale = 1f, - threshold = entry.name.EndsWith("_On") ? 1f : 0f + threshold = animation.name.EndsWith("_On") ? 1f : 0f }); } paramBlendTree.children = children.ToArray(); - rootBlendTree.children = rootBlendTree.children.Append(new ChildMotion - { - motion = paramBlendTree, - timeScale = 1f, - directBlendParameter = "YOTS_Weight" + // Add that blendtree to the parent direct blendtree (DBT) + // controlled by YOTS_Weight. That YOTS_Weight parameter is + // always set to 1, so the child blendtree always runs. + rootBlendTree.children = rootBlendTree.children.Append( + new ChildMotion{ + motion = paramBlendTree, + timeScale = 1f, + directBlendParameter = "YOTS_Weight" }).ToArray(); } - var baseState = baseStateMachine.AddState("BaseLayer_State"); + var baseState = baseStateMachine.AddState("YOTS_BaseLayer_State"); baseState.motion = rootBlendTree; baseState.writeDefaultValues = true; baseStateMachine.defaultState = baseState; - controller.AddLayer(new AnimatorControllerLayer - { + controller.AddLayer(new AnimatorControllerLayer{ name = "YOTS_BaseLayer", defaultWeight = 1.0f, stateMachine = baseStateMachine }); - // Add override layers - for (int i = 1; i < animatorConfig.layers.Count; i++) - { + // Add override layers. These are DBTs of On animations (no Off + // animations). + for (int i = 1; i < animatorConfig.layers.Count; i++) { var layerConfig = animatorConfig.layers[i]; string layerName = $"YOTS_OverrideLayer{(i-1).ToString("00")}"; @@ -393,15 +356,12 @@ namespace YOTS blendTree.name = layerName + "_BlendTree"; blendTree.blendType = BlendTreeType.Direct; - foreach (var entry in layerConfig.directBlendTree.entries) - { - if (!animationClipCache.TryGetValue(entry.name, out AnimationClip clip)) - { + foreach (var entry in layerConfig.directBlendTree.entries) { + if (!animationClips.TryGetValue(entry.name, out AnimationClip clip)) { throw new InvalidOperationException($"Animation clip not found in memory: {entry.name}"); } - - blendTree.children = blendTree.children.Append(new ChildMotion - { + + blendTree.children = blendTree.children.Append(new ChildMotion{ motion = clip, timeScale = 1f, directBlendParameter = entry.parameter @@ -413,8 +373,7 @@ namespace YOTS state.writeDefaultValues = true; stateMachine.defaultState = state; - controller.AddLayer(new AnimatorControllerLayer - { + controller.AddLayer(new AnimatorControllerLayer{ name = layerName, defaultWeight = 1.0f, stateMachine = stateMachine @@ -426,15 +385,12 @@ namespace YOTS return controller; } - private static Dictionary TopologicalSortToggles(List toggleSpecs) - { + private static Dictionary TopologicalSortToggles(List toggleSpecs) { Dictionary> graph = new Dictionary>(); - foreach (var toggle in toggleSpecs) - { + foreach (var toggle in toggleSpecs) { if (!graph.ContainsKey(toggle.name)) graph[toggle.name] = new HashSet(); - foreach (var dep in toggle.dependencies) - { + foreach (var dep in toggle.dependencies) { if (!graph.ContainsKey(dep)) graph[dep] = new HashSet(); graph[dep].Add(toggle.name); @@ -442,36 +398,29 @@ namespace YOTS } Dictionary inDegree = new Dictionary(); - foreach (var toggle in toggleSpecs) - { + foreach (var toggle in toggleSpecs) { inDegree[toggle.name] = toggle.dependencies.Count; } Dictionary depths = new Dictionary(); Queue queue = new Queue(); - foreach (var pair in inDegree) - { - if (pair.Value == 0) - { + foreach (var pair in inDegree) { + if (pair.Value == 0) { queue.Enqueue(pair.Key); depths[pair.Key] = 0; } } int processedNodes = 0; - while (queue.Count > 0) - { + while (queue.Count > 0) { string current = queue.Dequeue(); processedNodes++; int currentDepth = depths[current]; - if (graph.ContainsKey(current)) - { - foreach (var neighbor in graph[current]) - { + if (graph.ContainsKey(current)) { + foreach (var neighbor in graph[current]) { inDegree[neighbor]--; - if (inDegree[neighbor] == 0) - { + if (inDegree[neighbor] == 0) { queue.Enqueue(neighbor); depths[neighbor] = currentDepth + 1; } @@ -479,21 +428,19 @@ namespace YOTS } } - if (processedNodes != toggleSpecs.Count) - { + if (processedNodes != toggleSpecs.Count) { var cycleNodes = toggleSpecs .Where(t => !depths.ContainsKey(t.name)) .Select(t => t.name) .ToList(); - + throw new System.Exception($"Dependency cycle detected in toggle specifications. Nodes involved: {string.Join(", ", cycleNodes)}"); } return depths; } - private static GeneratedAnimatorConfig GenerateNaiveAnimatorConfig(List toggleSpecs) - { + private static GeneratedAnimatorConfig GenerateNaiveAnimatorConfig(List toggleSpecs) { GeneratedAnimatorConfig genAnimatorConfig = new GeneratedAnimatorConfig(); // Sort toggles into layers Dictionary depths = TopologicalSortToggles(toggleSpecs); @@ -502,33 +449,30 @@ namespace YOTS .OrderBy(g => g.Key) .ToList(); // Add layers - for (int i = 0; i < togglesByDepth.Count; i++) - { + for (int i = 0; i < togglesByDepth.Count; i++) { var depthGroup = togglesByDepth[i]; AnimatorLayer layer = new AnimatorLayer(); layer.name = i == 0 ? "YOTS_BaseLayer" : $"YOTS_OverrideLayer{(i - 1).ToString("00")}"; - - foreach (var toggle in depthGroup) - { + + foreach (var toggle in depthGroup) { string paramName = toggle.name; if (!genAnimatorConfig.parameters.Any(p => p.name == paramName)) - genAnimatorConfig.parameters.Add(new AnimatorParameterSetting - { - name = paramName, - defaultValue = toggle.defaultValue + genAnimatorConfig.parameters.Add(new AnimatorParameterSetting{ + name = paramName, + defaultValue = toggle.defaultValue }); - layer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry { + layer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry{ name = toggle.name + "_On", parameter = paramName }); - layer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry { + layer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry{ name = toggle.name + "_Off", parameter = paramName }); } - + genAnimatorConfig.layers.Add(layer); } // Add animations @@ -537,27 +481,21 @@ namespace YOTS return genAnimatorConfig; } - private static GeneratedAnimationsConfig GenerateAnimationConfig(List toggleSpecs) - { + private static GeneratedAnimationsConfig GenerateAnimationConfig(List toggleSpecs) { GeneratedAnimationsConfig genAnimConfig = new GeneratedAnimationsConfig(); - foreach (var toggle in toggleSpecs) - { + foreach (var toggle in toggleSpecs) { GeneratedAnimationClipConfig onAnim = new GeneratedAnimationClipConfig(); onAnim.name = toggle.name + "_On"; - if (toggle.meshToggles != null) - { - foreach (var mesh in toggle.meshToggles) - { + if (toggle.meshToggles != null) { + foreach (var mesh in toggle.meshToggles) { onAnim.meshToggles.Add(new GeneratedMeshToggle { path = mesh, value = 1.0f }); } } - if (toggle.blendShapes != null) - { - foreach (var bs in toggle.blendShapes) - { - onAnim.blendShapes.Add(new GeneratedBlendShape { - path = bs.path, - blendShape = bs.blendShape, + if (toggle.blendShapes != null) { + foreach (var bs in toggle.blendShapes) { + onAnim.blendShapes.Add(new GeneratedBlendShape{ + path = bs.path, + blendShape = bs.blendShape, value = bs.onValue }); } @@ -566,20 +504,16 @@ namespace YOTS GeneratedAnimationClipConfig offAnim = new GeneratedAnimationClipConfig(); offAnim.name = toggle.name + "_Off"; - if (toggle.meshToggles != null) - { - foreach (var mesh in toggle.meshToggles) - { + if (toggle.meshToggles != null) { + foreach (var mesh in toggle.meshToggles) { offAnim.meshToggles.Add(new GeneratedMeshToggle { path = mesh, value = 0.0f }); } } - if (toggle.blendShapes != null) - { - foreach (var bs in toggle.blendShapes) - { - offAnim.blendShapes.Add(new GeneratedBlendShape { - path = bs.path, - blendShape = bs.blendShape, + if (toggle.blendShapes != null) { + foreach (var bs in toggle.blendShapes) { + offAnim.blendShapes.Add(new GeneratedBlendShape{ + path = bs.path, + blendShape = bs.blendShape, value = bs.offValue }); } @@ -589,16 +523,13 @@ namespace YOTS return genAnimConfig; } - private static GeneratedAnimatorConfig ApplyIndependentFixToAnimatorConfig(GeneratedAnimatorConfig genAnimatorConfig) - { - float GetOffValueForMesh(string path, List offList) - { + private static GeneratedAnimatorConfig ApplyIndependentFixToAnimatorConfig(GeneratedAnimatorConfig genAnimatorConfig) { + float GetOffValueForMesh(string path, List offList) { var offToggle = offList?.FirstOrDefault(mt => mt.path == path); return offToggle != null ? offToggle.value : 0.0f; } - float GetOffValueForBlend(string path, string blendShapeName, List offList) - { + float GetOffValueForBlend(string path, string blendShapeName, List offList) { var offBlend = offList?.FirstOrDefault(bs => bs.path == path && bs.blendShape == blendShapeName); return offBlend != null ? offBlend.value : 0.0f; } @@ -606,10 +537,8 @@ namespace YOTS Dictionary toggleAnimations = new Dictionary(); - foreach (var anim in genAnimatorConfig.animations) - { - if (anim.name.EndsWith("_On")) - { + foreach (var anim in genAnimatorConfig.animations) { + if (anim.name.EndsWith("_On")) { string toggleName = anim.name.Substring(0, anim.name.LastIndexOf("_On")); if (!toggleAnimations.ContainsKey(toggleName)) toggleAnimations[toggleName] = (null, null); @@ -617,8 +546,7 @@ namespace YOTS pair.on = anim; toggleAnimations[toggleName] = pair; } - else if (anim.name.EndsWith("_Off")) - { + else if (anim.name.EndsWith("_Off")) { string toggleName = anim.name.Substring(0, anim.name.LastIndexOf("_Off")); if (!toggleAnimations.ContainsKey(toggleName)) toggleAnimations[toggleName] = (null, null); @@ -629,11 +557,9 @@ namespace YOTS } Dictionary toggleToLayerIndex = new Dictionary(); - for (int i = 0; i < genAnimatorConfig.layers.Count; i++) - { + for (int i = 0; i < genAnimatorConfig.layers.Count; i++) { var layer = genAnimatorConfig.layers[i]; - foreach (var entry in layer.directBlendTree.entries) - { + foreach (var entry in layer.directBlendTree.entries) { string entryName = entry.name; string toggleName = entryName; if (toggleName.EndsWith("_On")) @@ -651,34 +577,27 @@ namespace YOTS } Dictionary> attributeToToggles = new Dictionary>(); - foreach (var kvp in toggleAnimations) - { + foreach (var kvp in toggleAnimations) { string toggleName = kvp.Key; var pair = kvp.Value; if (pair.on == null) continue; HashSet attributes = new HashSet(); - if (pair.on.meshToggles != null) - { - foreach (var mt in pair.on.meshToggles) - { + if (pair.on.meshToggles != null) { + foreach (var mt in pair.on.meshToggles) { string attr = "MeshToggle:" + mt.path; attributes.Add(attr); } } - if (pair.on.blendShapes != null) - { - foreach (var bs in pair.on.blendShapes) - { + if (pair.on.blendShapes != null) { + foreach (var bs in pair.on.blendShapes) { string attr = "BlendShape:" + bs.path + "/" + bs.blendShape; attributes.Add(attr); } } - foreach (var attr in attributes) - { - if (!attributeToToggles.TryGetValue(attr, out var set)) - { + foreach (var attr in attributes) { + if (!attributeToToggles.TryGetValue(attr, out var set)) { set = new HashSet(); attributeToToggles[attr] = set; } @@ -692,27 +611,22 @@ namespace YOTS if (baseLayer == null && genAnimatorConfig.layers.Count > 0) baseLayer = genAnimatorConfig.layers[0]; - foreach (var kvp in toggleAnimations) - { + foreach (var kvp in toggleAnimations) { string toggleName = kvp.Key; var pair = kvp.Value; int layerIndex = toggleToLayerIndex.ContainsKey(toggleName) ? toggleToLayerIndex[toggleName] : 0; bool isBase = (layerIndex == 0); - if (isBase) - { + if (isBase) { newAnimations.Add(pair.on); newAnimations.Add(pair.off); } - else - { + else { List independentMesh = new List(); List dependentMesh = new List(); - if (pair.on.meshToggles != null) - { - foreach (var mt in pair.on.meshToggles) - { + if (pair.on.meshToggles != null) { + foreach (var mt in pair.on.meshToggles) { string attr = "MeshToggle:" + mt.path; if (attributeToToggles[attr].Count == 1) independentMesh.Add(mt); @@ -724,10 +638,8 @@ namespace YOTS List independentBlend = new List(); List dependentBlend = new List(); - if (pair.on.blendShapes != null) - { - foreach (var bs in pair.on.blendShapes) - { + if (pair.on.blendShapes != null) { + foreach (var bs in pair.on.blendShapes) { string attr = "BlendShape:" + bs.path + "/" + bs.blendShape; if (attributeToToggles[attr].Count == 1) independentBlend.Add(bs); @@ -739,8 +651,7 @@ namespace YOTS bool hasIndependent = (independentMesh.Count > 0 || independentBlend.Count > 0); bool hasDependent = (dependentMesh.Count > 0 || dependentBlend.Count > 0); - if (hasIndependent && hasDependent) - { + if (hasIndependent && hasDependent) { GeneratedAnimationClipConfig dependentOn = new GeneratedAnimationClipConfig(); dependentOn.name = toggleName + "_Dependent_On"; dependentOn.meshToggles = dependentMesh; @@ -749,13 +660,13 @@ namespace YOTS GeneratedAnimationClipConfig dependentOff = new GeneratedAnimationClipConfig(); dependentOff.name = toggleName + "_Dependent_Off"; dependentOff.meshToggles = dependentMesh - .Select(mt => new GeneratedMeshToggle { + .Select(mt => new GeneratedMeshToggle{ path = mt.path, value = GetOffValueForMesh(mt.path, pair.off.meshToggles) }) .ToList(); dependentOff.blendShapes = dependentBlend - .Select(bs => new GeneratedBlendShape { + .Select(bs => new GeneratedBlendShape{ path = bs.path, blendShape = bs.blendShape, value = GetOffValueForBlend(bs.path, bs.blendShape, pair.off.blendShapes) @@ -770,13 +681,13 @@ namespace YOTS GeneratedAnimationClipConfig independentOff = new GeneratedAnimationClipConfig(); independentOff.name = toggleName + "_Independent_Off"; independentOff.meshToggles = independentMesh - .Select(mt => new GeneratedMeshToggle { + .Select(mt => new GeneratedMeshToggle{ path = mt.path, value = GetOffValueForMesh(mt.path, pair.off.meshToggles) }) .ToList(); independentOff.blendShapes = independentBlend - .Select(bs => new GeneratedBlendShape { + .Select(bs => new GeneratedBlendShape{ path = bs.path, blendShape = bs.blendShape, value = GetOffValueForBlend(bs.path, bs.blendShape, pair.off.blendShapes) @@ -789,31 +700,25 @@ namespace YOTS newAnimations.Add(independentOff); AnimatorLayer overrideLayer = genAnimatorConfig.layers[layerIndex]; - foreach (var entry in overrideLayer.directBlendTree.entries) - { + foreach (var entry in overrideLayer.directBlendTree.entries) { if (entry.name.StartsWith(toggleName) && - (entry.name.EndsWith("_On") || entry.name.EndsWith("_Off"))) - { + (entry.name.EndsWith("_On") || entry.name.EndsWith("_Off"))) { entry.name = entry.name.EndsWith("_On") ? toggleName + "_Dependent_On" : toggleName + "_Dependent_Off"; } } - if (baseLayer != null) - { - baseLayer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry - { + if (baseLayer != null) { + baseLayer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry{ name = toggleName + "_Independent_On", parameter = toggleName }); - baseLayer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry - { + baseLayer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry{ name = toggleName + "_Independent_Off", parameter = toggleName }); } } - else if (hasIndependent && !hasDependent) - { + else if (hasIndependent && !hasDependent) { GeneratedAnimationClipConfig independentOn = new GeneratedAnimationClipConfig(); independentOn.name = toggleName + "_Independent_On"; independentOn.meshToggles = pair.on.meshToggles; @@ -828,22 +733,18 @@ namespace YOTS AnimatorLayer overrideLayer = genAnimatorConfig.layers[layerIndex]; overrideLayer.directBlendTree.entries.RemoveAll(e => e.name.StartsWith(toggleName)); - if (baseLayer != null) - { - baseLayer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry - { + if (baseLayer != null) { + baseLayer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry{ name = toggleName + "_Independent_On", parameter = toggleName }); - baseLayer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry - { + baseLayer.directBlendTree.entries.Add(new AnimatorDirectBlendTreeEntry{ name = toggleName + "_Independent_Off", parameter = toggleName }); } } - else if (!hasIndependent && hasDependent) - { + else if (!hasIndependent && hasDependent) { GeneratedAnimationClipConfig dependentOn = new GeneratedAnimationClipConfig(); dependentOn.name = toggleName + "_Dependent_On"; dependentOn.meshToggles = pair.on.meshToggles; @@ -857,11 +758,9 @@ namespace YOTS newAnimations.Add(dependentOff); AnimatorLayer overrideLayer = genAnimatorConfig.layers[layerIndex]; - foreach (var entry in overrideLayer.directBlendTree.entries) - { + foreach (var entry in overrideLayer.directBlendTree.entries) { if (entry.name.StartsWith(toggleName) && - (entry.name.EndsWith("_On") || entry.name.EndsWith("_Off"))) - { + (entry.name.EndsWith("_On") || entry.name.EndsWith("_Off"))) { entry.name = entry.name.EndsWith("_On") ? toggleName + "_Dependent_On" : toggleName + "_Dependent_Off"; } } @@ -873,21 +772,17 @@ namespace YOTS return genAnimatorConfig; } - private static GeneratedAnimatorConfig RemoveOffAnimationsFromOverrideLayers(GeneratedAnimatorConfig config) - { - for (int i = 1; i < config.layers.Count; i++) - { + private static GeneratedAnimatorConfig RemoveOffAnimationsFromOverrideLayers(GeneratedAnimatorConfig config) { + for (int i = 1; i < config.layers.Count; i++) { var layer = config.layers[i]; layer.directBlendTree.entries.RemoveAll(entry => entry.name.EndsWith("_Off")); } return config; } - private static GeneratedAnimatorConfig RemoveUnusedAnimations(GeneratedAnimatorConfig config) - { + private static GeneratedAnimatorConfig RemoveUnusedAnimations(GeneratedAnimatorConfig config) { HashSet referencedAnimations = new HashSet(); - foreach (var layer in config.layers) - { + foreach (var layer in config.layers) { foreach (var entry in layer.directBlendTree.entries) referencedAnimations.Add(entry.name); } @@ -900,19 +795,16 @@ namespace YOTS } private static VRCExpressionsMenu GetOrCreateSubmenu( - VRCExpressionsMenu parentMenu, + VRCExpressionsMenu parentMenu, string submenuName - ) - { + ) { if (parentMenu.controls == null) parentMenu.controls = new List(); // Check if submenu already exists - foreach (var control in parentMenu.controls) - { + foreach (var control in parentMenu.controls) { if (control.type == VRCExpressionsMenu.Control.ControlType.SubMenu && - control.name == submenuName && control.subMenu != null) - { + control.name == submenuName && control.subMenu != null) { // Clone existing submenu to avoid modifying original var clonedSubmenu = UnityEngine.Object.Instantiate(control.subMenu); control.subMenu = clonedSubmenu; @@ -925,8 +817,7 @@ namespace YOTS newSubmenu.name = submenuName; newSubmenu.controls = new List(); - var newControl = new VRCExpressionsMenu.Control - { + var newControl = new VRCExpressionsMenu.Control{ name = submenuName, type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = newSubmenu @@ -936,31 +827,26 @@ namespace YOTS return newSubmenu; } - private static void InitializeSubmenu(VRCExpressionsMenu menu) - { + private static void InitializeSubmenu(VRCExpressionsMenu menu) { if (menu == null) return; - - if (menu.controls != null) - { - foreach (var control in menu.controls) - { + + if (menu.controls != null) { + foreach (var control in menu.controls) { if (control.type == VRCExpressionsMenu.Control.ControlType.SubMenu && control.subMenu != null) InitializeSubmenu(control.subMenu); } menu.controls.Clear(); } - else - { + else { menu.controls = new List(); } } private static void GenerateVRChatAssets( - List toggleSpecs, + List toggleSpecs, VRCExpressionParameters vrcParams, VRCExpressionsMenu vrcMenu - ) - { + ) { var uniqueToggles = toggleSpecs .Where(t => t.name != "YOTS_Weight") .GroupBy(t => t.name) @@ -969,10 +855,8 @@ namespace YOTS var paramList = new List(); paramList.AddRange(vrcParams.parameters.Where(p => !uniqueToggles.Any(t => t.name == p.name))); - foreach (var toggle in uniqueToggles) - { - paramList.Add(new VRCExpressionParameters.Parameter - { + foreach (var toggle in uniqueToggles) { + paramList.Add(new VRCExpressionParameters.Parameter{ name = toggle.name, valueType = toggle.type == "radial" ? VRCExpressionParameters.ValueType.Float : VRCExpressionParameters.ValueType.Bool, defaultValue = toggle.defaultValue, @@ -987,35 +871,28 @@ namespace YOTS yotsSubmenu.controls = new List(); // Track all created/modified menus to ensure they're saved HashSet modifiedMenus = new HashSet { vrcMenu, yotsSubmenu }; - foreach (var toggle in toggleSpecs) - { + foreach (var toggle in toggleSpecs) { VRCExpressionsMenu currentMenu = yotsSubmenu; - if (!string.IsNullOrEmpty(toggle.menuPath) && toggle.menuPath != "/") - { + if (!string.IsNullOrEmpty(toggle.menuPath) && toggle.menuPath != "/") { string trimmedPath = toggle.menuPath.Trim('/'); var sections = trimmedPath.Split('/'); - foreach (var section in sections) - { + foreach (var section in sections) { currentMenu = GetOrCreateSubmenu(currentMenu, section); modifiedMenus.Add(currentMenu); } } // Add toggle controls - if (toggle.type == "radial") - { - currentMenu.controls.Add(new VRCExpressionsMenu.Control - { + if (toggle.type == "radial") { + currentMenu.controls.Add(new VRCExpressionsMenu.Control{ name = toggle.name, type = VRCExpressionsMenu.Control.ControlType.RadialPuppet, - subParameters = new VRCExpressionsMenu.Control.Parameter[] { + subParameters = new VRCExpressionsMenu.Control.Parameter[]{ new VRCExpressionsMenu.Control.Parameter { name = toggle.name } } }); } - else - { - currentMenu.controls.Add(new VRCExpressionsMenu.Control - { + else { + currentMenu.controls.Add(new VRCExpressionsMenu.Control{ name = toggle.name, type = VRCExpressionsMenu.Control.ControlType.Toggle, parameter = new VRCExpressionsMenu.Control.Parameter { name = toggle.name }, @@ -1025,8 +902,7 @@ namespace YOTS } // Add YOTS submenu to main menu - vrcMenu.controls.Add(new VRCExpressionsMenu.Control - { + vrcMenu.controls.Add(new VRCExpressionsMenu.Control{ name = "YOTS", type = VRCExpressionsMenu.Control.ControlType.SubMenu, subMenu = yotsSubmenu diff --git a/Scripts/YOTSNDMFConfig.cs b/Scripts/YOTSNDMFConfig.cs index 0609d18..9b2e71e 100644 --- a/Scripts/YOTSNDMFConfig.cs +++ b/Scripts/YOTSNDMFConfig.cs @@ -7,13 +7,11 @@ using VRC.SDK3.Avatars.ScriptableObjects; namespace YOTS { [DisallowMultipleComponent] - public class YOTSNDMFConfig : MonoBehaviour - { + public class YOTSNDMFConfig : MonoBehaviour { [Tooltip("The JSON configuration file.")] public TextAsset jsonConfig; - void OnValidate() - { + void OnValidate() { gameObject.tag = "EditorOnly"; } } diff --git a/Scripts/YOTSNDMFGenerator.cs b/Scripts/YOTSNDMFGenerator.cs index c3d4054..525953c 100644 --- a/Scripts/YOTSNDMFGenerator.cs +++ b/Scripts/YOTSNDMFGenerator.cs @@ -16,8 +16,7 @@ using UnityEditor.Animations; namespace YOTS { - public class YOTSNDMFGenerator : Plugin - { + public class YOTSNDMFGenerator : Plugin { private readonly Localizer localizer = new Localizer("en-us", () => new List<(string, Func)> { ("en-us", key => key) @@ -25,8 +24,7 @@ namespace YOTS public override string DisplayName => "YOTS Animator Generator"; - protected override void Configure() - { + protected override void Configure() { // First pass: Retrieve and stash configuration InPhase(BuildPhase.Resolving) .Run("Cache YOTS Config", ctx => { @@ -82,8 +80,7 @@ namespace YOTS RuntimeAnimatorController originalAnimator = descriptor.baseAnimationLayers[4].animatorController; var menu = descriptor.expressionsMenu; var parameters = descriptor.expressionParameters; - if (parameters == null || menu == null) - { + if (parameters == null || menu == null) { ErrorReport.WithContextObject(descriptor, () => { ErrorReport.ReportException( new Exception("Missing required VRC assets"), @@ -115,8 +112,7 @@ namespace YOTS } // If no original animator, just assign the generated one. - if (originalAnimator == null) - { + if (originalAnimator == null) { descriptor.baseAnimationLayers[4].animatorController = generatedAnimator; return; } @@ -129,14 +125,11 @@ namespace YOTS } // Simply append generated params and layers to the original animator. - private static void MergeAnimatorControllers(Localizer localizer, AnimatorController original, AnimatorController generated) - { + private static void MergeAnimatorControllers(Localizer localizer, AnimatorController original, AnimatorController generated) { // Merge parameters from generated into original. - foreach (var genParam in generated.parameters) - { + foreach (var genParam in generated.parameters) { // This is an O(m*n) check but m and n should be small enough to not matter. - if (original.parameters.Any(p => p.name == genParam.name)) - { + if (original.parameters.Any(p => p.name == genParam.name)) { ErrorReport.WithContextObject(original, () => { ErrorReport.ReportException( new Exception($"Parameter '{genParam.name}' already exists"), @@ -149,12 +142,10 @@ namespace YOTS } // Append each YOTS layer after the original layers. - foreach (var genLayer in generated.layers) - { + foreach (var genLayer in generated.layers) { // This isn't strictly an error but if someone already has layers named // YOTS_* that's probably not on purpose. - if (original.layers.Any(l => l.name == genLayer.name)) - { + if (original.layers.Any(l => l.name == genLayer.name)) { ErrorReport.WithContextObject(original, () => { ErrorReport.ReportException( new Exception($"Layer '{genLayer.name}' already exists"), @@ -163,8 +154,7 @@ namespace YOTS }); return; } - var newLayer = new AnimatorControllerLayer - { + var newLayer = new AnimatorControllerLayer { name = genLayer.name, defaultWeight = genLayer.defaultWeight, stateMachine = genLayer.stateMachine @@ -173,8 +163,7 @@ namespace YOTS } } - private class YOTSBuildState - { + private class YOTSBuildState { public string jsonConfig; } } -- cgit v1.2.3