diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/AssetBundleConverter.cs b/asset-bundle-converter/Assets/AssetBundleConverter/AssetBundleConverter.cs index 8c71fa83..125e2894 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/AssetBundleConverter.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/AssetBundleConverter.cs @@ -12,6 +12,7 @@ using GLTFast; using System.Diagnostics; using UnityEditor; +using UnityEditor.Animations; using UnityEngine; using UnityEngine.Networking; using UnityEngine.Profiling; @@ -24,6 +25,12 @@ namespace DCL.ABConverter { public class AssetBundleConverter { + public struct ConversionParams + { + public IReadOnlyList rawContents; + public string entityType; + } + private struct GltfImportSettings { public string url; @@ -99,6 +106,8 @@ public enum Step private bool isExitForced = false; private IABLogger log => env.logger; + private Dictionary downloadedData = new(); + private string entityType; public AssetBundleConverter(Environment env, ClientSettings settings) { @@ -119,10 +128,12 @@ public AssetBundleConverter(Environment env, ClientSettings settings) /// /// Entry point of the AssetBundleConverter /// - /// + /// /// - public async Task ConvertAsync(IReadOnlyList rawContents) + public async Task ConvertAsync(ConversionParams conversionParams) { + var rawContents = conversionParams.rawContents; + entityType = conversionParams.entityType; startupAllocated = Profiler.GetTotalAllocatedMemoryLong() / 100000.0; startupReserved = Profiler.GetTotalReservedMemoryLong() / 100000.0; @@ -281,9 +292,11 @@ private async Task 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 @@ -325,10 +338,10 @@ private async Task ProcessAllGltfs() embedExtractTextureTime.Start(); + string directory = Path.GetDirectoryName(relativePath); + if (textures.Count > 0) { - string directory = Path.GetDirectoryName(relativePath); - textures = ExtractEmbedTexturesFromGltf(textures, directory); } @@ -338,10 +351,15 @@ private async Task 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) @@ -417,6 +435,48 @@ private async Task 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 textures, GltfImportSettings gltf, IGltfImport gltfImport, string gltfUrl) { Profiler.BeginSample("ExtractEmbedMaterials"); @@ -808,7 +868,6 @@ private void PopulateLowercaseMappings(IReadOnlyListAn array containing all the assets to be dumped. /// true if succeeded /// - private Dictionary downloadedData = new(); private async Task DownloadAssets(IReadOnlyList rawContents) { diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/Editor/CustomGltfImporter.cs b/asset-bundle-converter/Assets/AssetBundleConverter/Editor/CustomGltfImporter.cs index cf084d78..a5fea35f 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/Editor/CustomGltfImporter.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/Editor/CustomGltfImporter.cs @@ -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; @@ -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); @@ -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(); + + if (animator != null) + { + var folderName = $"{Path.GetDirectoryName(ctx.assetPath)}/Animator/"; + string filePath = folderName + "animatorController.controller"; + AnimatorController animationController = AssetDatabase.LoadAssetAtPath(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(); + + if (animator == null) + base.CreateAnimationClips(ctx); + } + } + protected override void PreProcessGameObjects(GameObject sceneGo) { var meshFilters = sceneGo.GetComponentsInChildren(); @@ -164,9 +201,11 @@ private List 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; diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/SceneClient.cs b/asset-bundle-converter/Assets/AssetBundleConverter/SceneClient.cs index 123aca1d..80bb2789 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/SceneClient.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/SceneClient.cs @@ -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 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); } /// @@ -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() => @@ -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); } /// @@ -324,7 +327,7 @@ private static AssetBundleConverter.State GetUnexpectedResult() => /// The cid list for the scenes to gather from the catalyst's content server /// Any conversion settings object, if its null, a new one will be created /// A state context object useful for tracking the conversion progress - private static async Task ConvertEntitiesToAssetBundles(IReadOnlyList mappingPairs, ClientSettings settings) + private static async Task ConvertEntitiesToAssetBundles(IReadOnlyList mappingPairs, string entityType, ClientSettings settings) { if (mappingPairs == null || mappingPairs.Count == 0) { @@ -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; } } diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/Tests/AssetBundleConverterShould.cs b/asset-bundle-converter/Assets/AssetBundleConverter/Tests/AssetBundleConverterShould.cs index 1c71d2a4..049dc408 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/Tests/AssetBundleConverterShould.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/Tests/AssetBundleConverterShould.cs @@ -85,7 +85,7 @@ private void ThrowIfExitCodeIsNotZero(int exitCode) [Test] public async Task LoadVisualSceneOnStart() { - await converter.ConvertAsync(new List()); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); await editor.Received().LoadVisualTestSceneAsync(); } @@ -93,7 +93,7 @@ public async Task LoadVisualSceneOnStart() [Test] public async Task InitializeDirectories() { - await converter.ConvertAsync(new List()); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); directory.Received(1).InitializeDirectory(Arg.Is(EXAMPLE_AB_PATH), Arg.Any()); } @@ -112,7 +112,7 @@ public async Task TextureAssetIsProcessed() var manifest = Substitute.For(); buildPipeline.BuildAssetBundles(Arg.Any(), Arg.Any(), Arg.Any()).Returns(manifest); - await converter.ConvertAsync(new List { exampleAsset }); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); // Ensure that web request is done webRequest.Received().Get(Arg.Is(exampleBaseURL)); @@ -151,7 +151,7 @@ public async Task GltfAssetIsProcessed() var gltf = Substitute.For(); gltfImporter.GetImporter(Arg.Any(), Arg.Any>(), Arg.Any(), Arg.Any()).Returns(gltf); - gltfImporter.ConfigureImporter(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); + gltfImporter.ConfigureImporter(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); assetDatabase.LoadAssetAtPath(PathUtils.FullPathToAssetPath(assetPath.finalPath)).Returns(dummyGo); gltf.LoadingDone.Returns(true); @@ -159,7 +159,7 @@ public async Task GltfAssetIsProcessed() gltf.TextureCount.Returns(0); gltf.MaterialCount.Returns(0); - await converter.ConvertAsync(new List { exampleAsset }); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); // Ensure that web request is done webRequest.Received().Get(Arg.Is(exampleBaseURL)); @@ -177,7 +177,7 @@ public async Task GltfAssetIsProcessed() await gltf.Received().Load(Arg.Any(), Arg.Any()); // Ensure that the imported is properly configured - gltfImporter.Received().ConfigureImporter(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); + gltfImporter.Received().ConfigureImporter(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); // Ensure that asset was marked for asset bundle build directory.Received().MarkFolderForAssetBundleBuild(assetPath.finalPath, assetPath.hash); @@ -206,7 +206,7 @@ public async Task TextureIsExtractedFromGltf() exampleTexture.name = textureName; gltf.GetTexture(0).Returns(exampleTexture); - await converter.ConvertAsync(new List { exampleAsset }); + await converter.ConvertAsync(new DCL.ABConverter.AssetBundleConverter.ConversionParams()); var texturePath = $"{DOWNLOAD_FOLDER}{hash}{separator}Textures{separator}{textureName}.png"; @@ -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(), Arg.Any())).Do(c => Assert.AreNotEqual(c.Arg(), material)); - await converter.ConvertAsync(new List { 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(m => m.GetTexture("_BaseMap")), Arg.Any()); @@ -266,7 +266,7 @@ private IGltfImport ConfigureGltf(ContentServerUtils.MappingPair mappingPair) buildPipeline.BuildAssetBundles(Arg.Any(), Arg.Any(), Arg.Any()).Returns(Substitute.For()); var gltf = Substitute.For(); gltfImporter.GetImporter(Arg.Any(), Arg.Any>(), Arg.Any(), Arg.Any()).Returns(gltf); - gltfImporter.ConfigureImporter(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); + gltfImporter.ConfigureImporter(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); assetDatabase.LoadAssetAtPath(PathUtils.FullPathToAssetPath(assetPath.finalPath)).Returns(dummyGo); return gltf; } diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/DefaultGltfImporter.cs b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/DefaultGltfImporter.cs index cb2606f2..80363d99 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/DefaultGltfImporter.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/DefaultGltfImporter.cs @@ -39,7 +39,8 @@ private static IMaterialGenerator GetNewMaterialGenerator(ShaderType shaderType, ? new AssetBundleConverterMaterialGenerator(AssetBundleConverterMaterialGenerator.UseNewShader(buildTarget)) : null; - public bool ConfigureImporter(string relativePath, ContentMap[] contentMap, string fileRootPath, string hash, ShaderType shaderType ) + public bool ConfigureImporter(string relativePath, ContentMap[] contentMap, string fileRootPath, string hash, ShaderType shaderType, + AnimationMethod animationMethod) { var gltfImporter = AssetImporter.GetAtPath(relativePath) as CustomGltfImporter; if (gltfImporter != null) @@ -47,7 +48,7 @@ public bool ConfigureImporter(string relativePath, ContentMap[] contentMap, stri gltfImporter.SetupCustomFileProvider(contentMap, fileRootPath, hash); gltfImporter.useOriginalMaterials = shaderType == ShaderType.GlTFast; - gltfImporter.importSettings.AnimationMethod = AnimationMethod.Legacy; + gltfImporter.importSettings.AnimationMethod = animationMethod; gltfImporter.importSettings.GenerateMipMaps = false; assetDatabase.SaveImporter(gltfImporter); diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/GltfImportWrapper.cs b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/GltfImportWrapper.cs index 13d6f276..fd42eab7 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/GltfImportWrapper.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Implementations/Default/GltfImportWrapper.cs @@ -3,6 +3,7 @@ using GLTFast.Logging; using GLTFast.Materials; using System; +using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; @@ -38,6 +39,9 @@ public Texture2D GetTexture(int index) => public Material GetMaterial(int index) => importer.GetMaterial(index); + public IReadOnlyList GetClips() => + importer.GetAnimationClips(); + public void Dispose() { importer.Dispose(); diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImport.cs b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImport.cs index ad5d3467..078cbfb4 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImport.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImport.cs @@ -1,5 +1,6 @@ using GLTFast; using GLTFast.Logging; +using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; @@ -19,6 +20,8 @@ public interface IGltfImport Material GetMaterial(int index); + IReadOnlyList GetClips(); + void Dispose(); Material defaultMaterial { get; } diff --git a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImporter.cs b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImporter.cs index 27113238..d6aba4e8 100644 --- a/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImporter.cs +++ b/asset-bundle-converter/Assets/AssetBundleConverter/Wrappers/Interfaces/IGltfImporter.cs @@ -1,5 +1,6 @@ using AssetBundleConverter.Editor; using DCL.ABConverter; +using GLTFast; using System.Collections.Generic; using UnityEditor; @@ -9,6 +10,7 @@ public interface IGltfImporter { IGltfImport GetImporter(AssetPath filePath, Dictionary contentTable, ShaderType shaderType, BuildTarget buildTarget); - bool ConfigureImporter(string relativePath, ContentMap[] contentMap, string fileRootPath, string hash, ShaderType shaderType); + bool ConfigureImporter(string relativePath, ContentMap[] contentMap, string fileRootPath, string hash, ShaderType shaderType, + AnimationMethod animationMethod); } } diff --git a/asset-bundle-converter/Assets/git-submodules/glTFast b/asset-bundle-converter/Assets/git-submodules/glTFast index 4fc8a079..5abe11cd 160000 --- a/asset-bundle-converter/Assets/git-submodules/glTFast +++ b/asset-bundle-converter/Assets/git-submodules/glTFast @@ -1 +1 @@ -Subproject commit 4fc8a079fc08f52a26130b319f4f69897b0e2c93 +Subproject commit 5abe11cd53e9686787b5478a03ab095fa0d86c1c