Skip to content

Commit

Permalink
feat: desktop animations (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kinerius authored Mar 20, 2024
1 parent 11e39c0 commit 7bf7eda
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using GLTFast;
using System.Diagnostics;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Profiling;
Expand All @@ -24,6 +25,12 @@ namespace DCL.ABConverter
{
public class AssetBundleConverter
{
public struct ConversionParams
{
public IReadOnlyList<ContentServerUtils.MappingPair> rawContents;
public string entityType;
}

private struct GltfImportSettings
{
public string url;
Expand Down Expand Up @@ -99,6 +106,8 @@ public enum Step

private bool isExitForced = false;
private IABLogger log => env.logger;
private Dictionary<AssetPath, byte[]> downloadedData = new();
private string entityType;

public AssetBundleConverter(Environment env, ClientSettings settings)
{
Expand All @@ -119,10 +128,12 @@ public AssetBundleConverter(Environment env, ClientSettings settings)
/// <summary>
/// Entry point of the AssetBundleConverter
/// </summary>
/// <param name="rawContents"></param>
/// <param name="conversionParams"></param>
/// <returns></returns>
public async Task ConvertAsync(IReadOnlyList<ContentServerUtils.MappingPair> rawContents)
public async Task ConvertAsync(ConversionParams conversionParams)
{
var rawContents = conversionParams.rawContents;
entityType = conversionParams.entityType;
startupAllocated = Profiler.GetTotalAllocatedMemoryLong() / 100000.0;
startupReserved = Profiler.GetTotalReservedMemoryLong() / 100000.0;

Expand Down Expand Up @@ -281,9 +292,11 @@ private async Task<bool> ProcessAllGltfs()

ContentMap[] contentMap = contentTable.Select(kvp => new ContentMap(kvp.Key, kvp.Value)).ToArray();

AnimationMethod animationMethod = GetAnimationMethod();

var importSettings = new ImportSettings
{
AnimationMethod = AnimationMethod.Legacy,
AnimationMethod = animationMethod,
NodeNameMethod = NameImportMethod.OriginalUnique,
AnisotropicFilterLevel = 0,
GenerateMipMaps = false
Expand Down Expand Up @@ -325,10 +338,10 @@ private async Task<bool> ProcessAllGltfs()

embedExtractTextureTime.Start();

string directory = Path.GetDirectoryName(relativePath);

if (textures.Count > 0)
{
string directory = Path.GetDirectoryName(relativePath);

textures = ExtractEmbedTexturesFromGltf(textures, directory);
}

Expand All @@ -338,10 +351,15 @@ private async Task<bool> ProcessAllGltfs()
ExtractEmbedMaterialsFromGltf(textures, gltf, gltfImport, gltfUrl);
embedExtractMaterialTime.Stop();

if (animationMethod == AnimationMethod.Mecanim)
{
CreateAnimatorController(gltfImport, directory);
}

log.Verbose($"Importing {relativePath}");

configureGltftime.Start();
bool importerSuccess = env.gltfImporter.ConfigureImporter(relativePath, contentMap, gltf.AssetPath.fileRootPath, gltf.AssetPath.hash, settings.shaderType);
bool importerSuccess = env.gltfImporter.ConfigureImporter(relativePath, contentMap, gltf.AssetPath.fileRootPath, gltf.AssetPath.hash, settings.shaderType, animationMethod);
configureGltftime.Stop();

if (importerSuccess)
Expand Down Expand Up @@ -417,6 +435,48 @@ private async Task<bool> ProcessAllGltfs()
return false;
}

private void CreateAnimatorController(IGltfImport gltfImport, string directory)
{
var animatorRoot = $"{directory}/Animator/";

if (!env.directory.Exists(animatorRoot))
env.directory.CreateDirectory(animatorRoot);

var filePath = $"{animatorRoot}animatorController.controller";
var controller = AnimatorController.CreateAnimatorControllerAtPath(filePath);
var clips = gltfImport.GetClips();

var rootStateMachine = controller.layers[0].stateMachine;

foreach (AnimationClip animationClip in clips)
{
// copy the animation asset so we dont use the same references that will get disposed
var newCopy = Object.Instantiate(animationClip);
newCopy.name = animationClip.name;

// embed clip into the animatorController
AssetDatabase.AddObjectToAsset(newCopy, controller);
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(newCopy));

// configure the animator
string animationClipName = newCopy.name;
controller.AddParameter(animationClipName, AnimatorControllerParameterType.Trigger);
var state = controller.AddMotion(newCopy, 0);
var anyStateTransition = rootStateMachine.AddAnyStateTransition(state);
anyStateTransition.AddCondition(AnimatorConditionMode.If, 0, animationClipName);
anyStateTransition.duration = 0;
}

AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}

private AnimationMethod GetAnimationMethod()
{
if (!entityType.ToLower().Contains("emote")) return AnimationMethod.Legacy;
return settings.buildTarget is BuildTarget.StandaloneWindows64 or BuildTarget.StandaloneOSX ? AnimationMethod.Mecanim : AnimationMethod.Legacy;
}

private void ExtractEmbedMaterialsFromGltf(List<Texture2D> textures, GltfImportSettings gltf, IGltfImport gltfImport, string gltfUrl)
{
Profiler.BeginSample("ExtractEmbedMaterials");
Expand Down Expand Up @@ -808,7 +868,6 @@ private void PopulateLowercaseMappings(IReadOnlyList<ContentServerUtils.MappingP
/// <param name="rawContents">An array containing all the assets to be dumped.</param>
/// <returns>true if succeeded</returns>
///
private Dictionary<AssetPath, byte[]> downloadedData = new();

private async Task<bool> DownloadAssets(IReadOnlyList<ContentServerUtils.MappingPair> rawContents)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using GLTFast.Editor;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Animations;
using UnityEditor.AssetImporters;
using UnityEngine;
using Object = UnityEngine.Object;
Expand Down Expand Up @@ -75,7 +76,12 @@ public override void OnImportAsset(AssetImportContext ctx)
if (!useOriginalMaterials)
SetupCustomMaterialGenerator(new AssetBundleConverterMaterialGenerator(AssetBundleConverterMaterialGenerator.UseNewShader(EditorUserBuildSettings.activeBuildTarget)));

try { base.OnImportAsset(ctx); }
try
{
base.OnImportAsset(ctx);

ReferenceAnimatorController(ctx);
}
catch (Exception e)
{
Debug.LogError("UNCAUGHT FATAL: Failed to Import GLTF " + hash);
Expand All @@ -84,6 +90,37 @@ public override void OnImportAsset(AssetImportContext ctx)
}
}

private void ReferenceAnimatorController(AssetImportContext ctx)
{
GameObject gameObject = ctx.mainObject as GameObject;
if (gameObject != null)
{
Animator animator = gameObject.GetComponent<Animator>();

if (animator != null)
{
var folderName = $"{Path.GetDirectoryName(ctx.assetPath)}/Animator/";
string filePath = folderName + "animatorController.controller";
AnimatorController animationController = AssetDatabase.LoadAssetAtPath<AnimatorController>(filePath);
animator.runtimeAnimatorController = animationController;
}
}
}

// When creating Animators we embed the clips into the animator controller, so we prevent duplicating the clips here
protected override void CreateAnimationClips(AssetImportContext ctx)
{
GameObject gameObject = ctx.mainObject as GameObject;

if (gameObject != null)
{
Animator animator = gameObject.GetComponent<Animator>();

if (animator == null)
base.CreateAnimationClips(ctx);
}
}

protected override void PreProcessGameObjects(GameObject sceneGo)
{
var meshFilters = sceneGo.GetComponentsInChildren<MeshFilter>();
Expand Down Expand Up @@ -164,9 +201,11 @@ private List<Material> ReplaceMaterials(string folderName, Renderer[] renderers)
Material validDefaultMaterial = GetExtractedMaterial(folderName, m_Gltf.defaultMaterial.name);
materials.Add(validDefaultMaterial);
ReplaceReferences(renderers, validDefaultMaterial);

foreach (Renderer renderer in renderers)
{
var sharedMaterials = renderer.sharedMaterials;

for (var i = 0; i < sharedMaterials.Length; i++)
if (sharedMaterials[i] == null)
sharedMaterials[i] = validDefaultMaterial;
Expand Down
19 changes: 13 additions & 6 deletions asset-bundle-converter/Assets/AssetBundleConverter/SceneClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,13 +280,15 @@ private static void ParseCommonSettings(string[] commandLineArgs, ClientSettings
var apiResponse = await Utils.GetEntityMappingsAsync(settings.targetHash, settings, env.webRequest);
if (apiResponse == null) return GetUnexpectedResult();
var mappings = apiResponse.SelectMany(m => m.content);
return await ConvertEntitiesToAssetBundles(mappings.ToArray(), settings);
string entityType = apiResponse[0].type;
return await ConvertEntitiesToAssetBundles(mappings.ToArray(), entityType, settings);
}

public static async Task<AssetBundleConverter.State> ConvertEmptyScenesByMapping(ClientSettings settings)
{
EnsureEnvironment(settings.BuildPipelineType);
return await ConvertEntitiesToAssetBundles(await Utils.GetEmptyScenesMappingAsync(settings.targetHash, settings, env.webRequest), settings);
ContentServerUtils.MappingPair[] emptyScenesMapping = await Utils.GetEmptyScenesMappingAsync(settings.targetHash, settings, env.webRequest);
return await ConvertEntitiesToAssetBundles(emptyScenesMapping, "scene", settings);
}

/// <summary>
Expand All @@ -302,7 +304,8 @@ private static void ParseCommonSettings(string[] commandLineArgs, ClientSettings
var apiResponse = await Utils.GetEntityMappings(settings.targetPointer.Value, settings, env.webRequest);
if (apiResponse == null) return GetUnexpectedResult();
var mappings = apiResponse.SelectMany(m => m.content);
return await ConvertEntitiesToAssetBundles(mappings.ToArray(), settings);
var entityType = apiResponse[0].type;
return await ConvertEntitiesToAssetBundles(mappings.ToArray(), entityType, settings);
}

private static AssetBundleConverter.State GetUnexpectedResult() =>
Expand All @@ -315,7 +318,7 @@ private static AssetBundleConverter.State GetUnexpectedResult() =>
settings.isWearable = true;

var mappings = await WearablesClient.GetCollectionMappingsAsync(settings.targetHash, ContentServerUtils.ApiTLD.ORG, env.webRequest);
return await ConvertEntitiesToAssetBundles(mappings, settings);
return await ConvertEntitiesToAssetBundles(mappings, "wearable", settings);
}

/// <summary>
Expand All @@ -324,7 +327,7 @@ private static AssetBundleConverter.State GetUnexpectedResult() =>
/// <param name="entitiesId">The cid list for the scenes to gather from the catalyst's content server</param>
/// <param name="settings">Any conversion settings object, if its null, a new one will be created</param>
/// <returns>A state context object useful for tracking the conversion progress</returns>
private static async Task<AssetBundleConverter.State> ConvertEntitiesToAssetBundles(IReadOnlyList<ContentServerUtils.MappingPair> mappingPairs, ClientSettings settings)
private static async Task<AssetBundleConverter.State> ConvertEntitiesToAssetBundles(IReadOnlyList<ContentServerUtils.MappingPair> mappingPairs, string entityType, ClientSettings settings)
{
if (mappingPairs == null || mappingPairs.Count == 0)
{
Expand All @@ -339,7 +342,11 @@ private static AssetBundleConverter.State GetUnexpectedResult() =>
EnsureEnvironment(settings.BuildPipelineType);

var core = new AssetBundleConverter(env, settings);
await core.ConvertAsync(mappingPairs);
await core.ConvertAsync(new AssetBundleConverter.ConversionParams
{
rawContents = mappingPairs,
entityType = entityType,
});
return core.CurrentState;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ private void ThrowIfExitCodeIsNotZero(int exitCode)
[Test]
public async Task LoadVisualSceneOnStart()
{
await converter.ConvertAsync(new List<ContentServerUtils.MappingPair>());
await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams());

await editor.Received().LoadVisualTestSceneAsync();
}

[Test]
public async Task InitializeDirectories()
{
await converter.ConvertAsync(new List<ContentServerUtils.MappingPair>());
await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams());

directory.Received(1).InitializeDirectory(Arg.Is(EXAMPLE_AB_PATH), Arg.Any<bool>());
}
Expand All @@ -112,7 +112,7 @@ public async Task TextureAssetIsProcessed()
var manifest = Substitute.For<IAssetBundleManifest>();
buildPipeline.BuildAssetBundles(Arg.Any<string>(), Arg.Any<BuildAssetBundleOptions>(), Arg.Any<BuildTarget>()).Returns(manifest);

await converter.ConvertAsync(new List<ContentServerUtils.MappingPair> { exampleAsset });
await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams());

// Ensure that web request is done
webRequest.Received().Get(Arg.Is(exampleBaseURL));
Expand Down Expand Up @@ -151,15 +151,15 @@ public async Task GltfAssetIsProcessed()
var gltf = Substitute.For<IGltfImport>();

gltfImporter.GetImporter(Arg.Any<AssetPath>(), Arg.Any<Dictionary<string, string>>(), Arg.Any<ShaderType>(), Arg.Any<BuildTarget>()).Returns(gltf);
gltfImporter.ConfigureImporter(Arg.Any<string>(), Arg.Any<ContentMap[]>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<ShaderType>()).Returns(true);
gltfImporter.ConfigureImporter(Arg.Any<string>(), Arg.Any<ContentMap[]>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<ShaderType>(), Arg.Any<AnimationMethod>()).Returns(true);
assetDatabase.LoadAssetAtPath<GameObject>(PathUtils.FullPathToAssetPath(assetPath.finalPath)).Returns(dummyGo);

gltf.LoadingDone.Returns(true);
gltf.LoadingError.Returns(false);
gltf.TextureCount.Returns(0);
gltf.MaterialCount.Returns(0);

await converter.ConvertAsync(new List<ContentServerUtils.MappingPair> { exampleAsset });
await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams());

// Ensure that web request is done
webRequest.Received().Get(Arg.Is(exampleBaseURL));
Expand All @@ -177,7 +177,7 @@ public async Task GltfAssetIsProcessed()
await gltf.Received().Load(Arg.Any<string>(), Arg.Any<ImportSettings>());

// Ensure that the imported is properly configured
gltfImporter.Received().ConfigureImporter(Arg.Any<string>(), Arg.Any<ContentMap[]>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<ShaderType>());
gltfImporter.Received().ConfigureImporter(Arg.Any<string>(), Arg.Any<ContentMap[]>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<ShaderType>(), Arg.Any<AnimationMethod>());

// Ensure that asset was marked for asset bundle build
directory.Received().MarkFolderForAssetBundleBuild(assetPath.finalPath, assetPath.hash);
Expand Down Expand Up @@ -206,7 +206,7 @@ public async Task TextureIsExtractedFromGltf()
exampleTexture.name = textureName;
gltf.GetTexture(0).Returns(exampleTexture);

await converter.ConvertAsync(new List<ContentServerUtils.MappingPair> { exampleAsset });
await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams());

var texturePath = $"{DOWNLOAD_FOLDER}{hash}{separator}Textures{separator}{textureName}.png";

Expand Down Expand Up @@ -250,7 +250,7 @@ public async Task MaterialIsExtractedFromGltf()
// Ensure that a when the material asset is created, the new instance of the material is a copy
assetDatabase.When(ad => ad.CreateAsset(Arg.Any<Material>(), Arg.Any<string>())).Do(c => Assert.AreNotEqual(c.Arg<Material>(), material));

await converter.ConvertAsync(new List<ContentServerUtils.MappingPair> { exampleAsset });
await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams());

// Ensure that a material asset is created and the texture is set for this material
assetDatabase.Received(1).CreateAsset(Arg.Is<Material>(m => m.GetTexture("_BaseMap")), Arg.Any<string>());
Expand All @@ -266,7 +266,7 @@ private IGltfImport ConfigureGltf(ContentServerUtils.MappingPair mappingPair)
buildPipeline.BuildAssetBundles(Arg.Any<string>(), Arg.Any<BuildAssetBundleOptions>(), Arg.Any<BuildTarget>()).Returns(Substitute.For<IAssetBundleManifest>());
var gltf = Substitute.For<IGltfImport>();
gltfImporter.GetImporter(Arg.Any<AssetPath>(), Arg.Any<Dictionary<string, string>>(), Arg.Any<ShaderType>(), Arg.Any<BuildTarget>()).Returns(gltf);
gltfImporter.ConfigureImporter(Arg.Any<string>(), Arg.Any<ContentMap[]>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<ShaderType>()).Returns(true);
gltfImporter.ConfigureImporter(Arg.Any<string>(), Arg.Any<ContentMap[]>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<ShaderType>(), Arg.Any<AnimationMethod>()).Returns(true);
assetDatabase.LoadAssetAtPath<GameObject>(PathUtils.FullPathToAssetPath(assetPath.finalPath)).Returns(dummyGo);
return gltf;
}
Expand Down
Loading

0 comments on commit 7bf7eda

Please sign in to comment.