229 lines
No EOL
8.8 KiB
C#
229 lines
No EOL
8.8 KiB
C#
using System.Linq;
|
|
using System.Diagnostics;
|
|
using AnimatorAsCode.V1;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using VRC.SDK3.Avatars.Components;
|
|
using VRC.SDK3.Dynamics.Constraint.Components;
|
|
using System.Collections.Generic;
|
|
using UnityEditor.Animations;
|
|
using System;
|
|
|
|
|
|
namespace gay.lilyy.SoldAvatarBootstrap
|
|
{
|
|
|
|
|
|
|
|
public class AvatarAssets
|
|
{
|
|
public AacFlBase aac;
|
|
public GameObject root;
|
|
public AnimatorController fx;
|
|
public AacFlClip emptyClip;
|
|
public bool experimentalEnabled;
|
|
// public bool isPC;
|
|
}
|
|
|
|
|
|
public class AvatarBootstrapCore
|
|
{
|
|
|
|
private static AnimatorController GetController(AvatarDefinition definition)
|
|
{
|
|
string metaPath = definition.FXLayerPath + ".meta";
|
|
string originalGuid = null;
|
|
if (System.IO.File.Exists(metaPath))
|
|
{
|
|
foreach (var line in System.IO.File.ReadAllLines(metaPath))
|
|
{
|
|
if (line.StartsWith("guid: "))
|
|
{
|
|
originalGuid = line.Substring(6).Trim();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (AssetDatabase.LoadAssetAtPath<AnimatorController>(definition.FXLayerPath) != null)
|
|
{
|
|
AssetDatabase.DeleteAsset(definition.FXLayerPath);
|
|
AssetDatabase.Refresh();
|
|
}
|
|
var directory = System.IO.Path.GetDirectoryName(definition.FXLayerPath);
|
|
if (!AssetDatabase.IsValidFolder(directory))
|
|
{
|
|
System.IO.Directory.CreateDirectory(directory);
|
|
AssetDatabase.Refresh();
|
|
}
|
|
var newController = AnimatorController.CreateAnimatorControllerAtPath(definition.FXLayerPath);
|
|
if (originalGuid != null && System.IO.File.Exists(metaPath))
|
|
{
|
|
var lines = System.IO.File.ReadAllLines(metaPath);
|
|
for (int i = 0; i < lines.Length; ++i)
|
|
{
|
|
if (lines[i].StartsWith("guid: "))
|
|
{
|
|
lines[i] = "guid: " + originalGuid;
|
|
}
|
|
}
|
|
System.IO.File.WriteAllLines(metaPath, lines);
|
|
AssetDatabase.Refresh();
|
|
}
|
|
|
|
return AssetDatabase.LoadAssetAtPath<AnimatorController>(definition.FXLayerPath);
|
|
}
|
|
public static void RunForCurrentAvatar(AvatarDefinition definition)
|
|
{
|
|
|
|
var allLayerGroups = LayerGroup.Instances.ToList();
|
|
|
|
var definitionsToProcess = new HashSet<AvatarDefinition> { definition };
|
|
var definitionsToExpand = new Queue<AvatarDefinition>(definitionsToProcess);
|
|
while (definitionsToExpand.Count > 0)
|
|
{
|
|
var current = definitionsToExpand.Dequeue();
|
|
foreach (var extended in current.Extends)
|
|
{
|
|
if (definitionsToProcess.Add(extended))
|
|
{
|
|
definitionsToExpand.Enqueue(extended);
|
|
}
|
|
}
|
|
}
|
|
|
|
var layerGroups = allLayerGroups
|
|
.Where(lg => lg.TargetDefinitions.Any(definitionsToProcess.Contains))
|
|
.ToList();
|
|
|
|
// Start overall timer
|
|
var overallStopwatch = Stopwatch.StartNew();
|
|
|
|
AvatarLogger.LogInfo("Starting SoldAvatarBootstrap generation for definition: " + definition.DisplayName);
|
|
GameObject root = null;
|
|
foreach (var candidate in GameObject.FindObjectsOfType<VRCAvatarDescriptor>(true))
|
|
{
|
|
if (PrefabUtility.IsPartOfPrefabAsset(candidate.gameObject))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (definition.IsApplicable(candidate.gameObject))
|
|
{
|
|
AvatarLogger.LogInfo($"Found suitable avatar: {candidate.gameObject.name}");
|
|
root = candidate.gameObject;
|
|
break;
|
|
}
|
|
}
|
|
if (root == null)
|
|
{
|
|
AvatarLogger.LogError($"No suitable avatar found for definition: {definition.DisplayName}");
|
|
return;
|
|
}
|
|
|
|
// If the avatar is part of a prefab instance, use the instance root so we create overrides.
|
|
if (PrefabUtility.IsPartOfPrefabInstance(root))
|
|
{
|
|
root = PrefabUtility.GetNearestPrefabInstanceRoot(root);
|
|
}
|
|
|
|
// Skip if the root GameObject or the component itself is disabled
|
|
if (!root.activeSelf || !root.gameObject.activeInHierarchy)
|
|
{
|
|
AvatarLogger.LogWarning($"Avatar root '{root.name}' is disabled or inactive. Skipping.");
|
|
return;
|
|
}
|
|
|
|
AvatarAssets assets = new()
|
|
{
|
|
root = root,
|
|
|
|
// isPC = EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows
|
|
// || EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows64,
|
|
|
|
fx = GetController(definition)
|
|
};
|
|
|
|
|
|
// Time AAC initialization
|
|
var initStopwatch = Stopwatch.StartNew();
|
|
AvatarLogger.LogInfo("Initializing Animator As Code...");
|
|
assets.aac = AacV1.Create(new AacConfiguration
|
|
{
|
|
SystemName = definition.SystemName,
|
|
AnimatorRoot = root.transform,
|
|
DefaultValueRoot = root.transform,
|
|
AssetKey = GUID.Generate().ToString(),
|
|
AssetContainer = assets.fx,
|
|
ContainerMode = AacConfiguration.Container.Everything,
|
|
DefaultsProvider = new AacDefaultsProvider(true)
|
|
});
|
|
assets.emptyClip = assets.aac.NewClip();
|
|
|
|
initStopwatch.Stop();
|
|
AvatarLogger.LogInfo($"AAC initialization completed in {initStopwatch.ElapsedMilliseconds}ms");
|
|
|
|
// Process layer groups with individual timing
|
|
// Filter out the template LayerGroup (by SystemName)
|
|
// AACLogger.LogInfo($"Processing {layerGroups.Count} layer groups...");
|
|
|
|
AvatarLogger.LogInfo($"Processing {layerGroups.Count} layer groups...");
|
|
var totalLayerGroupTime = 0L;
|
|
var processedCount = 0;
|
|
var skippedCount = 0;
|
|
|
|
foreach (var layerGroup in layerGroups)
|
|
{
|
|
var layerStopwatch = Stopwatch.StartNew();
|
|
|
|
bool skipped = false;
|
|
|
|
if (!layerGroup.enabled)
|
|
{
|
|
AvatarLogger.LogWarning($"Skipping layer group: {layerGroup.DisplayName} (disabled)");
|
|
skipped = true;
|
|
}
|
|
|
|
if (!skipped)
|
|
{
|
|
AvatarLogger.LogInfo($"Running layer group: {layerGroup.DisplayName}");
|
|
layerGroup.Run(assets);
|
|
processedCount++;
|
|
}
|
|
else
|
|
{
|
|
skippedCount++;
|
|
}
|
|
|
|
layerStopwatch.Stop();
|
|
totalLayerGroupTime += layerStopwatch.ElapsedMilliseconds;
|
|
AvatarLogger.LogInfo($"Layer group '{layerGroup.DisplayName}' completed in {layerStopwatch.ElapsedMilliseconds}ms");
|
|
}
|
|
|
|
// Final timing summary
|
|
overallStopwatch.Stop();
|
|
AvatarLogger.LogSuccess($"SoldAvatarBootstrap generation completed successfully!");
|
|
AvatarLogger.LogInfo($"=== TIMING SUMMARY ===");
|
|
AvatarLogger.LogInfo($"Total time: {overallStopwatch.ElapsedMilliseconds}ms ({overallStopwatch.Elapsed.TotalSeconds:F2}s)");
|
|
AvatarLogger.LogInfo($"AAC initialization: {initStopwatch.ElapsedMilliseconds}ms");
|
|
AvatarLogger.LogInfo($"Layer groups: {totalLayerGroupTime}ms (processed: {processedCount}, skipped: {skippedCount})");
|
|
AvatarLogger.LogInfo($"Average per layer group: {(processedCount > 0 ? totalLayerGroupTime / processedCount : 0)}ms");
|
|
|
|
// replace the animator controller
|
|
var descriptor = root.GetComponent<VRCAvatarDescriptor>();
|
|
var animatorControllers = descriptor.baseAnimationLayers
|
|
.ToList();
|
|
animatorControllers.RemoveAll(layer => layer.type == VRCAvatarDescriptor.AnimLayerType.FX);
|
|
animatorControllers.Add(new VRCAvatarDescriptor.CustomAnimLayer
|
|
{
|
|
type = VRCAvatarDescriptor.AnimLayerType.FX,
|
|
animatorController = assets.fx
|
|
});
|
|
descriptor.baseAnimationLayers = animatorControllers.ToArray();
|
|
|
|
if (PrefabUtility.IsPartOfPrefabInstance(root))
|
|
{
|
|
PrefabUtility.RecordPrefabInstancePropertyModifications(descriptor);
|
|
}
|
|
}
|
|
}
|
|
} |