From 3006a70d5cbbec7e47c09723e8294a9d77802331 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts <49847914+ds5678@users.noreply.github.com> Date: Thu, 8 Jun 2023 10:01:25 -0400 Subject: [PATCH] Texture Array Export on 2020.2+ * Resolves #425 * Resolves #426 --- .../Ripper.cs | 9 ++ .../Textures/ImporterFactory.cs | 142 ++++++++++++++---- .../TextureArrayAssetExportCollection.cs | 23 +++ .../Textures/TextureArrayAssetExporter.cs | 90 +++++++++++ .../Textures/TextureConverter.cs | 28 ++++ .../Textures/TextureExportCollection.cs | 6 +- .../CubemapArrayExtensions.cs | 38 +++++ .../Texture3DExtensions.cs | 30 +++- 8 files changed, 334 insertions(+), 32 deletions(-) create mode 100644 Source/AssetRipper.Export.UnityProjects/Textures/TextureArrayAssetExportCollection.cs create mode 100644 Source/AssetRipper.Export.UnityProjects/Textures/TextureArrayAssetExporter.cs create mode 100644 Source/AssetRipper.SourceGenerated.Extensions/CubemapArrayExtensions.cs diff --git a/Source/AssetRipper.Export.UnityProjects/Ripper.cs b/Source/AssetRipper.Export.UnityProjects/Ripper.cs index 4804e878b..fe2e63181 100644 --- a/Source/AssetRipper.Export.UnityProjects/Ripper.cs +++ b/Source/AssetRipper.Export.UnityProjects/Ripper.cs @@ -254,6 +254,15 @@ namespace AssetRipper.Export.UnityProjects projectExporter.OverrideExporter(spriteExporter); } + //Texture Array exporters + if (Settings.Version.IsGreaterEqual(2020, 2)) + { + TextureArrayAssetExporter textureArrayExporter = new(Settings); + projectExporter.OverrideExporter(textureArrayExporter); + projectExporter.OverrideExporter(textureArrayExporter); + projectExporter.OverrideExporter(textureArrayExporter); + } + //Shader exporters projectExporter.OverrideExporter(Settings.ShaderExportMode switch { diff --git a/Source/AssetRipper.Export.UnityProjects/Textures/ImporterFactory.cs b/Source/AssetRipper.Export.UnityProjects/Textures/ImporterFactory.cs index 265753e47..94fefd206 100644 --- a/Source/AssetRipper.Export.UnityProjects/Textures/ImporterFactory.cs +++ b/Source/AssetRipper.Export.UnityProjects/Textures/ImporterFactory.cs @@ -1,11 +1,16 @@ -using AssetRipper.Assets.Export; +using AssetRipper.Assets; +using AssetRipper.Assets.Export; using AssetRipper.Import.Logging; using AssetRipper.SourceGenerated.Classes.ClassID_1006; using AssetRipper.SourceGenerated.Classes.ClassID_1055; +using AssetRipper.SourceGenerated.Classes.ClassID_117; +using AssetRipper.SourceGenerated.Classes.ClassID_187; +using AssetRipper.SourceGenerated.Classes.ClassID_188; using AssetRipper.SourceGenerated.Classes.ClassID_28; using AssetRipper.SourceGenerated.Classes.ClassID_89; using AssetRipper.SourceGenerated.Enums; using AssetRipper.SourceGenerated.Extensions; +using AssetRipper.SourceGenerated.Subclasses.GLTextureSettings; using AssetRipper.SourceGenerated.Subclasses.TextureImporterPlatformSettings; using System.Numerics; @@ -13,40 +18,39 @@ namespace AssetRipper.Export.UnityProjects.Textures { public static class ImporterFactory { - public static ITextureImporter GenerateTextureImporter(IExportContainer container, ITexture2D origin) + public static ITextureImporter GenerateTextureImporter(IExportContainer container, IUnityObjectBase origin) { + TextureImporterData data = new TextureImporterData(origin); ITextureImporter instance = TextureImporterFactory.CreateAsset(container.ExportVersion, container.File); - instance.MipMaps_C1006.EnableMipMap = (origin.Has_MipCount_C28() && origin.MipCount_C28 > 1 || origin.Has_MipMap_C28() && origin.MipMap_C28) ? 1 : 0; - instance.MipMaps_C1006.SRGBTexture = origin.ColorSpace_C28 == (int)ColorSpace.Linear ? 1 : 0; + instance.MipMaps_C1006.EnableMipMap = data.EnableMipMap ? 1 : 0; + instance.MipMaps_C1006.SRGBTexture = data.SRGBTexture ? 1 : 0; instance.MipMaps_C1006.AlphaTestReferenceValue = 0.5f; instance.MipMaps_C1006.MipMapFadeDistanceStart = 1; instance.MipMaps_C1006.MipMapFadeDistanceEnd = 3; instance.BumpMap_C1006.HeightScale = .25f; - instance.GenerateCubemap_C1006 = (int)TextureImporterGenerateCubemap.AutoCubemap; - instance.StreamingMipmaps_C1006 = origin.StreamingMipmaps_C28 ? 1 : 0; - instance.StreamingMipmapsPriority_C1006 = origin.StreamingMipmapsPriority_C28; - instance.IsReadable_C1006 = origin.IsReadable_C28 ? 1 : 0; - instance.Format_C1006 = origin.Format_C28; - instance.MaxTextureSize_C1006 = CalculateMaxTextureSize(origin.Width_C28, origin.Height_C28); - instance.TextureSettings_C1006.CopyValues(origin.TextureSettings_C28); - instance.NPOTScale_C1006 = (int)TextureImporterNPOTScale.ToNearest; // Default texture importer settings uses this value, and cubemaps appear to not work when it's None + instance.GenerateCubemap_C1006E = TextureImporterGenerateCubemap.AutoCubemap; + instance.StreamingMipmaps_C1006 = data.StreamingMipmaps ? 1 : 0; + instance.StreamingMipmapsPriority_C1006 = data.StreamingMipmapsPriority; + instance.IsReadable_C1006 = data.IsReadable ? 1 : 0; + instance.Format_C1006 = (int)data.Format; + instance.MaxTextureSize_C1006 = data.MaxTextureSize; + instance.TextureSettings_C1006.CopyValues(data.TextureSettings); + instance.NPOTScale_C1006E = TextureImporterNPOTScale.ToNearest; // Default texture importer settings uses this value, and cubemaps appear to not work when it's None instance.CompressionQuality_C1006 = 50; instance.SetSwizzle(TextureImporterSwizzle.R, TextureImporterSwizzle.G, TextureImporterSwizzle.B, TextureImporterSwizzle.A); - instance.SpriteMode_C1006 = (int)SpriteImportMode.Single; + instance.SpriteMode_C1006E = SpriteImportMode.Single; instance.SpriteExtrude_C1006 = 1; - instance.SpriteMeshType_C1006 = (int)SpriteMeshType.Tight; + instance.SpriteMeshType_C1006E = SpriteMeshType.Tight; instance.SpritePivot_C1006?.SetValues(0.5f, 0.5f); instance.SpritePixelsToUnits_C1006 = 100.0f; instance.SpriteGenerateFallbackPhysicsShape_C1006 = 1; - instance.AlphaUsage_C1006 = (int)TextureImporterAlphaSource.FromInput; + instance.AlphaUsage_C1006E = TextureImporterAlphaSource.FromInput; instance.AlphaIsTransparency_C1006 = 1; instance.SpriteTessellationDetail_C1006 = -1; - instance.TextureType_C1006 = GetTextureTypeFromLightmapFormat(origin); - instance.TextureShape_C1006 = origin is ICubemap - ? (int)TextureImporterShape.TextureCube - : (int)TextureImporterShape.Texture2D; + instance.TextureType_C1006E = data.TextureType; + instance.TextureShape_C1006E = data.TextureShape; ITextureImporterPlatformSettings platformSettings = instance.PlatformSettings_C1006.AddNew(); platformSettings.BuildTarget.String = "DefaultTexturePlatform"; @@ -67,18 +71,100 @@ namespace AssetRipper.Export.UnityProjects.Textures } return instance; + } - static int CalculateMaxTextureSize(int width, int height) - { - uint maxSideLength = (uint)Math.Max(width, height); - return Math.Max(2048, (int)BitOperations.RoundUpToPowerOf2(maxSideLength)); - } + private static int CalculateMaxTextureSize(int width, int height) + { + uint maxSideLength = (uint)Math.Max(width, height); + return Math.Max(2048, (int)BitOperations.RoundUpToPowerOf2(maxSideLength)); + } - static int GetTextureTypeFromLightmapFormat(ITexture2D origin) + private readonly ref struct TextureImporterData + { + public bool EnableMipMap { get; } + public bool SRGBTexture { get; } + public bool StreamingMipmaps { get; } + public int StreamingMipmapsPriority { get; } + public bool IsReadable { get; } + public TextureFormat Format { get; } + public int MaxTextureSize { get; } + public IGLTextureSettings? TextureSettings { get; } + public TextureImporterType TextureType { get; } + public TextureImporterShape TextureShape { get; } + + public TextureImporterData(IUnityObjectBase asset) { - return ((TextureUsageMode)origin.LightmapFormat_C28).IsNormalmap() - ? (int)TextureImporterType.NormalMap - : (int)TextureImporterType.Default; + switch (asset) + { + case ITexture2D texture2D: + { + EnableMipMap = texture2D.Has_MipCount_C28() && texture2D.MipCount_C28 > 1 || texture2D.Has_MipMap_C28() && texture2D.MipMap_C28; + SRGBTexture = texture2D.ColorSpace_C28 == (int)ColorSpace.Linear; + StreamingMipmaps = texture2D.StreamingMipmaps_C28; + StreamingMipmapsPriority = texture2D.StreamingMipmapsPriority_C28; + IsReadable = texture2D.IsReadable_C28; + Format = texture2D.Format_C28E; + MaxTextureSize = CalculateMaxTextureSize(texture2D.Width_C28, texture2D.Height_C28); + TextureSettings = texture2D.TextureSettings_C28; + TextureType = ((TextureUsageMode)texture2D.LightmapFormat_C28).IsNormalmap() + ? TextureImporterType.NormalMap + : TextureImporterType.Default; + TextureShape = texture2D is ICubemap + ? TextureImporterShape.TextureCube + : TextureImporterShape.Texture2D; + } + break; + case ITexture2DArray texture2DArray: + { + EnableMipMap = texture2DArray.MipCount_C187 > 1; + SRGBTexture = texture2DArray.ColorSpace_C187E == ColorSpace.Linear; + StreamingMipmaps = false; + StreamingMipmapsPriority = default; + IsReadable = texture2DArray.IsReadable_C187; + Format = texture2DArray.Format_C187E; + MaxTextureSize = CalculateMaxTextureSize(texture2DArray.Width_C187, texture2DArray.Height_C187); + TextureSettings = texture2DArray.TextureSettings_C187; + TextureType = texture2DArray.Has_UsageMode_C187() && ((TextureUsageMode)texture2DArray.UsageMode_C187).IsNormalmap() + ? TextureImporterType.NormalMap + : TextureImporterType.Default; + TextureShape = TextureImporterShape.Texture2DArray; + } + break; + case ICubemapArray cubemapArray: + { + EnableMipMap = cubemapArray.MipCount_C188 > 1; + SRGBTexture = cubemapArray.ColorSpace_C188 == (int)ColorSpace.Linear; + StreamingMipmaps = false; + StreamingMipmapsPriority = default; + IsReadable = cubemapArray.IsReadable_C188; + Format = cubemapArray.Format_C188E; + MaxTextureSize = CalculateMaxTextureSize(cubemapArray.Width_C188, cubemapArray.Width_C188); + TextureSettings = cubemapArray.TextureSettings_C188; + TextureType = cubemapArray.Has_UsageMode_C188() && ((TextureUsageMode)cubemapArray.UsageMode_C188).IsNormalmap() + ? TextureImporterType.NormalMap + : TextureImporterType.Default; + TextureShape = TextureImporterShape.Texture2DArray;//Maybe this should be TextureCube + } + break; + case ITexture3D texture3D: + { + EnableMipMap = texture3D.Has_MipCount_C117() && texture3D.MipCount_C117 > 1 || texture3D.Has_MipMap_C117() && texture3D.MipMap_C117; + SRGBTexture = texture3D.ColorSpace_C117 == (int)ColorSpace.Linear; + StreamingMipmaps = false; + StreamingMipmapsPriority = default; + IsReadable = texture3D.IsReadable_C117; + Format = texture3D.GetTextureFormat(); + MaxTextureSize = CalculateMaxTextureSize(texture3D.Width_C117, texture3D.Height_C117); + TextureSettings = texture3D.TextureSettings_C117; + TextureType = texture3D.GetLightmapFormat().IsNormalmap() + ? TextureImporterType.NormalMap + : TextureImporterType.Default; + TextureShape = TextureImporterShape.Texture3D; + } + break; + default: + throw new ArgumentException($"Asset type not supported: {asset.GetType().Name}", nameof(asset)); + } } } diff --git a/Source/AssetRipper.Export.UnityProjects/Textures/TextureArrayAssetExportCollection.cs b/Source/AssetRipper.Export.UnityProjects/Textures/TextureArrayAssetExportCollection.cs new file mode 100644 index 000000000..867129759 --- /dev/null +++ b/Source/AssetRipper.Export.UnityProjects/Textures/TextureArrayAssetExportCollection.cs @@ -0,0 +1,23 @@ +using AssetRipper.Assets; +using AssetRipper.Assets.Export; +using AssetRipper.Export.UnityProjects.Configuration; +using AssetRipper.Export.UnityProjects.Project.Collections; + +namespace AssetRipper.Export.UnityProjects.Textures; + +public sealed class TextureArrayAssetExportCollection : AssetExportCollection +{ + public TextureArrayAssetExportCollection(TextureArrayAssetExporter assetExporter, IUnityObjectBase asset) : base(assetExporter, asset) + { + } + + protected override string GetExportExtension(IUnityObjectBase asset) + { + return ((TextureArrayAssetExporter)AssetExporter).ImageExportFormat.GetFileExtension(); + } + + protected override IUnityObjectBase CreateImporter(IExportContainer container) + { + return ImporterFactory.GenerateTextureImporter(container, Asset); + } +} diff --git a/Source/AssetRipper.Export.UnityProjects/Textures/TextureArrayAssetExporter.cs b/Source/AssetRipper.Export.UnityProjects/Textures/TextureArrayAssetExporter.cs new file mode 100644 index 000000000..72583f4af --- /dev/null +++ b/Source/AssetRipper.Export.UnityProjects/Textures/TextureArrayAssetExporter.cs @@ -0,0 +1,90 @@ +using AssetRipper.Assets; +using AssetRipper.Assets.Collections; +using AssetRipper.Assets.Export; +using AssetRipper.Export.UnityProjects.Configuration; +using AssetRipper.Export.UnityProjects.Project.Exporters; +using AssetRipper.Export.UnityProjects.Utils; +using AssetRipper.Import.Logging; +using AssetRipper.SourceGenerated.Classes.ClassID_117; +using AssetRipper.SourceGenerated.Classes.ClassID_187; +using AssetRipper.SourceGenerated.Classes.ClassID_188; +using AssetRipper.SourceGenerated.Classes.ClassID_28; +using AssetRipper.SourceGenerated.Extensions; +using AssetRipper.SourceGenerated.Subclasses.StreamingInfo; + +namespace AssetRipper.Export.UnityProjects.Textures; + +public sealed class TextureArrayAssetExporter : BinaryAssetExporter +{ + public ImageExportFormat ImageExportFormat { get; private set; } + + public TextureArrayAssetExporter(LibraryConfiguration configuration) + { + ImageExportFormat = configuration.ImageExportFormat; + } + + public override bool TryCreateCollection(IUnityObjectBase asset, TemporaryAssetCollection temporaryFile, [NotNullWhen(true)] out IExportCollection? exportCollection) + { + exportCollection = asset switch + { + ITexture2D texture when texture.CheckAssetIntegrity() => new TextureArrayAssetExportCollection(this, texture), + _ => null, + }; + return exportCollection is not null; + } + + public override bool Export(IExportContainer container, IUnityObjectBase asset, string path) + { + DirectBitmap? bitmap; + switch (asset) + { + case ICubemapArray cubemapArray: + { + if (!cubemapArray.CheckAssetIntegrity()) + { + WarnResourceFileNotFound(cubemapArray.NameString, cubemapArray.StreamData_C188); + return false; + } + bitmap = TextureConverter.ConvertToBitmap(cubemapArray); + } + break; + case ITexture2DArray texture2DArray: + { + if (!texture2DArray.CheckAssetIntegrity()) + { + WarnResourceFileNotFound(texture2DArray.NameString, texture2DArray.StreamData_C187); + return false; + } + bitmap = TextureConverter.ConvertToBitmap(texture2DArray); + } + break; + case ITexture3D texture3D: + { + if (!texture3D.CheckAssetIntegrity()) + { + WarnResourceFileNotFound(texture3D.NameString, texture3D.StreamData_C117); + return false; + } + bitmap = TextureConverter.ConvertToBitmap(texture3D); + } + break; + default: + { + Logger.Log(LogType.Error, LogCategory.Export, $"Texture array '{asset}' has unsupported type '{asset.GetType().Name}'"); + } + return false; + } + + if (bitmap is null) + { + Logger.Log(LogType.Warning, LogCategory.Export, $"Unable to convert '{asset}' to bitmap"); + return false; + } + return bitmap.Save(path, ImageExportFormat); + + static void WarnResourceFileNotFound(string assetName, IStreamingInfo? streamingInfo) + { + Logger.Log(LogType.Warning, LogCategory.Export, $"Can't export '{assetName}' because resources file '{streamingInfo?.Path}' hasn't been found"); + } + } +} diff --git a/Source/AssetRipper.Export.UnityProjects/Textures/TextureConverter.cs b/Source/AssetRipper.Export.UnityProjects/Textures/TextureConverter.cs index a625352bb..eae649ace 100644 --- a/Source/AssetRipper.Export.UnityProjects/Textures/TextureConverter.cs +++ b/Source/AssetRipper.Export.UnityProjects/Textures/TextureConverter.cs @@ -2,6 +2,7 @@ using AssetRipper.Export.UnityProjects.Utils; using AssetRipper.Import.Logging; using AssetRipper.SourceGenerated.Classes.ClassID_117; using AssetRipper.SourceGenerated.Classes.ClassID_187; +using AssetRipper.SourceGenerated.Classes.ClassID_188; using AssetRipper.SourceGenerated.Classes.ClassID_28; using AssetRipper.SourceGenerated.Classes.ClassID_89; using AssetRipper.SourceGenerated.Enums; @@ -80,6 +81,33 @@ namespace AssetRipper.Export.UnityProjects.Textures return bitmap; } + public static DirectBitmap? ConvertToBitmap(ICubemapArray texture) + { + byte[] buffer = texture.GetImageData(); + if (buffer.Length == 0) + { + return null; + } + + DirectBitmap? bitmap = ConvertToBitmap( + texture.Format_C188E, + texture.Width_C188, + texture.Width_C188,//Not sure if this is correct + texture.CubemapCount_C188 * 6,//Not sure if this is correct + (int)texture.DataSize_C188, + texture.Collection.Version, + buffer); + + if (bitmap == null) + { + return null; + } + + bitmap.FlipY(); + + return bitmap; + } + public static DirectBitmap? ConvertToBitmap(ITexture2D texture) { byte[] buffer = texture.GetImageData(); diff --git a/Source/AssetRipper.Export.UnityProjects/Textures/TextureExportCollection.cs b/Source/AssetRipper.Export.UnityProjects/Textures/TextureExportCollection.cs index 2ddab954d..a2e8f53f7 100644 --- a/Source/AssetRipper.Export.UnityProjects/Textures/TextureExportCollection.cs +++ b/Source/AssetRipper.Export.UnityProjects/Textures/TextureExportCollection.cs @@ -78,7 +78,7 @@ namespace AssetRipper.Export.UnityProjects.Textures return exportID; } - private void AddSprites(ITextureImporter importer, Dictionary? textureSpriteInformation) + private void AddSprites(ITextureImporter importer, IReadOnlyDictionary? textureSpriteInformation) { if (textureSpriteInformation == null || textureSpriteInformation.Count == 0) { @@ -146,7 +146,7 @@ namespace AssetRipper.Export.UnityProjects.Textures } } - private static void AddSpriteSheet(ITextureImporter importer, Dictionary textureSpriteInformation) + private static void AddSpriteSheet(ITextureImporter importer, IReadOnlyDictionary textureSpriteInformation) { if (!importer.Has_SpriteSheet_C1006()) { @@ -173,7 +173,7 @@ namespace AssetRipper.Export.UnityProjects.Textures } } - private void AddIDToName(ITextureImporter importer, Dictionary textureSpriteInformation) + private void AddIDToName(ITextureImporter importer, IReadOnlyDictionary textureSpriteInformation) { if (importer.SpriteMode_C1006E == SpriteImportMode.Multiple) { diff --git a/Source/AssetRipper.SourceGenerated.Extensions/CubemapArrayExtensions.cs b/Source/AssetRipper.SourceGenerated.Extensions/CubemapArrayExtensions.cs new file mode 100644 index 000000000..7b2576c08 --- /dev/null +++ b/Source/AssetRipper.SourceGenerated.Extensions/CubemapArrayExtensions.cs @@ -0,0 +1,38 @@ +using AssetRipper.SourceGenerated.Classes.ClassID_188; + +namespace AssetRipper.SourceGenerated.Extensions; + +public static class CubemapArrayExtensions +{ + public static byte[] GetImageData(this ICubemapArray texture) + { + if (texture.ImageData_C188.Length > 0) + { + return texture.ImageData_C188; + } + else if (texture.Has_StreamData_C188() && texture.StreamData_C188.IsSet()) + { + return texture.StreamData_C188.GetContent(texture.Collection); + } + else + { + return Array.Empty(); + } + } + + public static bool CheckAssetIntegrity(this ICubemapArray texture) + { + if (texture.ImageData_C188.Length > 0) + { + return true; + } + else if (texture.Has_StreamData_C188()) + { + return texture.StreamData_C188.CheckIntegrity(texture.Collection); + } + else + { + return false; + } + } +} diff --git a/Source/AssetRipper.SourceGenerated.Extensions/Texture3DExtensions.cs b/Source/AssetRipper.SourceGenerated.Extensions/Texture3DExtensions.cs index 8bcf257fe..890596486 100644 --- a/Source/AssetRipper.SourceGenerated.Extensions/Texture3DExtensions.cs +++ b/Source/AssetRipper.SourceGenerated.Extensions/Texture3DExtensions.cs @@ -21,6 +21,22 @@ namespace AssetRipper.SourceGenerated.Extensions } } + public static bool CheckAssetIntegrity(this ITexture3D texture) + { + if (texture.ImageData_C117.Length > 0) + { + return true; + } + else if (texture.Has_StreamData_C117()) + { + return texture.StreamData_C117.CheckIntegrity(texture.Collection); + } + else + { + return false; + } + } + public static TextureFormat GetTextureFormat(this ITexture3D texture) { if (texture.Has_Format_C117_Int32()) @@ -33,6 +49,18 @@ namespace AssetRipper.SourceGenerated.Extensions } } + public static TextureUsageMode GetLightmapFormat(this ITexture3D texture) + { + if (texture.Has_LightmapFormat_C117()) + { + return texture.LightmapFormat_C117E; + } + else + { + return (TextureUsageMode)texture.UsageMode_C117; + } + } + public static int GetCompleteImageSize(this ITexture3D texture) { if (texture.Has_DataSize_C117()) @@ -56,7 +84,7 @@ namespace AssetRipper.SourceGenerated.Extensions if (texture.MipMap_C117) { int maxSide = Math.Max(texture.Width_C117, texture.Height_C117); - return Convert.ToInt32(Math.Log(maxSide) / Math.Log(2)); + return Convert.ToInt32(Math.Log2(maxSide)); } else {