Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: desktop animations #63

Merged
merged 5 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading