From 6693a1e0bad504916906aa161cec69365fcbb8a6 Mon Sep 17 00:00:00 2001 From: ackwell Date: Sat, 27 Jan 2024 13:53:56 +1100 Subject: [PATCH 1/6] Clear errors/warnings before starting IO --- .../ModEditWindow.Models.MdlTab.cs | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs index ace2bcfc..b3c0cacd 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs @@ -2,6 +2,7 @@ using Lumina.Data.Parsing; using OtterGui; using Penumbra.GameData; using Penumbra.GameData.Files; +using Penumbra.Import.Models; using Penumbra.Import.Models.Export; using Penumbra.Meta.Manipulations; using Penumbra.String.Classes; @@ -79,7 +80,7 @@ public partial class ModEditWindow return; } - PendingIo = true; + BeginIo(); var task = Task.Run(() => { // TODO: Is it worth trying to order results based on option priorities for cases where more than one match is found? @@ -93,9 +94,7 @@ public partial class ModEditWindow task.ContinueWith(t => { - RecordIoExceptions(t.Exception); - GamePaths = t.Result; - PendingIo = false; + GamePaths = FinalizeIo(task); }); } @@ -132,33 +131,22 @@ public partial class ModEditWindow return; } - PendingIo = true; + BeginIo(); _edit._models.ExportToGltf(ExportConfig, Mdl, sklbPaths, ReadFile, outputPath) - .ContinueWith(task => - { - RecordIoExceptions(task.Exception); - if (task is { IsCompletedSuccessfully: true, Result: not null }) - IoWarnings = task.Result.GetWarnings().ToList(); - PendingIo = false; - }); + .ContinueWith(FinalizeIo); } /// Import a model from an interchange format. /// Disk path to load model data from. public void Import(string inputPath) { - PendingIo = true; + BeginIo(); _edit._models.ImportGltf(inputPath) .ContinueWith(task => { - RecordIoExceptions(task.Exception); - if (task is { IsCompletedSuccessfully: true, Result: (not null, _) }) - { - IoWarnings = task.Result.Item2.GetWarnings().ToList(); - FinalizeImport(task.Result.Item1); - } - - PendingIo = false; + var mdlFile = FinalizeIo(task, result => result.Item1, result => result.Item2); + if (mdlFile != null) + FinalizeImport(mdlFile); }); } @@ -255,6 +243,34 @@ public partial class ModEditWindow target.ElementIds = [.. elementIds]; } + private void BeginIo() + { + PendingIo = true; + IoWarnings = []; + IoExceptions = []; + } + + private void FinalizeIo(Task task) + => FinalizeIo(task, notifier => null, notifier => notifier); + + private TResult? FinalizeIo(Task task) + => FinalizeIo(task, result => result, null); + + private TResult? FinalizeIo(Task task, Func getResult, Func? getNotifier) + { + TResult? result = default; + RecordIoExceptions(task.Exception); + if (task is { IsCompletedSuccessfully: true, Result: not null }) + { + result = getResult(task.Result); + if (getNotifier != null) + IoWarnings = getNotifier(task.Result).GetWarnings().ToList(); + } + PendingIo = false; + + return result; + } + private void RecordIoExceptions(Exception? exception) { IoExceptions = exception switch From 72775a80bfbcc1b7c7ab06827f1caf6722195a9d Mon Sep 17 00:00:00 2001 From: ackwell Date: Sat, 27 Jan 2024 14:08:56 +1100 Subject: [PATCH 2/6] Ignore invalid attributes on export --- Penumbra/Import/Models/Export/MeshExporter.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index 5347b87d..1a06acd1 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -230,10 +230,19 @@ public class MeshExporter { "targetNames", shapeNames }, }); - var attributes = Enumerable.Range(0, 32) - .Where(index => ((attributeMask >> index) & 1) == 1) - .Select(index => _mdl.Attributes[index]) - .ToArray(); + string[] attributes = []; + var maxAttribute = 31 - BitOperations.LeadingZeroCount(attributeMask); + if (maxAttribute < _mdl.Attributes.Length) + { + attributes = Enumerable.Range(0, 32) + .Where(index => ((attributeMask >> index) & 1) == 1) + .Select(index => _mdl.Attributes[index]) + .ToArray(); + } + else + { + _notifier.Warning($"Invalid attribute data, ignoring."); + } return new MeshData { From 1649da70a899fa90ad3ced2a37ff1d8e726b0a16 Mon Sep 17 00:00:00 2001 From: ackwell Date: Sat, 27 Jan 2024 17:56:32 +1100 Subject: [PATCH 3/6] Fix Blender 3.6 support for custom colour attribute --- Penumbra/Import/Models/Export/VertexFragment.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Penumbra/Import/Models/Export/VertexFragment.cs b/Penumbra/Import/Models/Export/VertexFragment.cs index 27d2ab10..08b2a214 100644 --- a/Penumbra/Import/Models/Export/VertexFragment.cs +++ b/Penumbra/Import/Models/Export/VertexFragment.cs @@ -11,7 +11,8 @@ and there's reason to overhaul the export pipeline. public struct VertexColorFfxiv : IVertexCustom { - [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)] + // NOTE: We only realistically require UNSIGNED_BYTE for this, however Blender 3.6 errors on that (fixed in 4.0). + [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, false)] public Vector4 FfxivColor; public int MaxColors => 0; @@ -80,7 +81,7 @@ public struct VertexTexture1ColorFfxiv : IVertexCustom [VertexAttribute("TEXCOORD_0")] public Vector2 TexCoord0; - [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)] + [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, false)] public Vector4 FfxivColor; public int MaxColors => 0; @@ -162,7 +163,7 @@ public struct VertexTexture2ColorFfxiv : IVertexCustom [VertexAttribute("TEXCOORD_1")] public Vector2 TexCoord1; - [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)] + [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, false)] public Vector4 FfxivColor; public int MaxColors => 0; From e9628afaf84ac8afc24eaeddc5a92f12c00e3f2f Mon Sep 17 00:00:00 2001 From: ackwell Date: Sat, 27 Jan 2024 18:35:26 +1100 Subject: [PATCH 4/6] Include specular factor in character material export --- .../Import/Models/Export/MaterialExporter.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs index 307e9d2b..cb2cf6f5 100644 --- a/Penumbra/Import/Models/Export/MaterialExporter.cs +++ b/Penumbra/Import/Models/Export/MaterialExporter.cs @@ -89,11 +89,15 @@ public class MaterialExporter // TODO: handle other textures stored in the mask? } + // Specular extension puts colour on RGB and factor on A. We're already packing like that, so we can reuse the texture. + var specularImage = BuildImage(specular, name, "specular"); + return BuildSharedBase(material, name) .WithBaseColor(BuildImage(baseColor, name, "basecolor")) .WithNormal(BuildImage(operation.Normal, name, "normal")) - .WithSpecularColor(BuildImage(specular, name, "specular")) - .WithEmissive(BuildImage(operation.Emissive, name, "emissive"), Vector3.One, 1); + .WithEmissive(BuildImage(operation.Emissive, name, "emissive"), Vector3.One, 1) + .WithSpecularFactor(specularImage, 1) + .WithSpecularColor(specularImage); } // TODO: It feels a little silly to request the entire normal here when extracting the normal only needs some of the components. @@ -102,7 +106,7 @@ public class MaterialExporter { public Image Normal { get; } = normal.Clone(); public Image BaseColor { get; } = new(normal.Width, normal.Height); - public Image Specular { get; } = new(normal.Width, normal.Height); + public Image Specular { get; } = new(normal.Width, normal.Height); public Image Emissive { get; } = new(normal.Width, normal.Height); private Buffer2D NormalBuffer @@ -111,7 +115,7 @@ public class MaterialExporter private Buffer2D BaseColorBuffer => BaseColor.Frames.RootFrame.PixelBuffer; - private Buffer2D SpecularBuffer + private Buffer2D SpecularBuffer => Specular.Frames.RootFrame.PixelBuffer; private Buffer2D EmissiveBuffer @@ -140,7 +144,9 @@ public class MaterialExporter // Specular (table) var lerpedSpecularColor = Vector3.Lerp(prevRow.Specular, nextRow.Specular, tableRow.Weight); - specularSpan[x].FromVector4(new Vector4(lerpedSpecularColor, 1)); + // float.Lerp is .NET8 ;-; + var lerpedSpecularFactor = prevRow.SpecularStrength * (1.0f - tableRow.Weight) + nextRow.SpecularStrength * tableRow.Weight; + specularSpan[x].FromVector4(new Vector4(lerpedSpecularColor, lerpedSpecularFactor)); // Emissive (table) var lerpedEmissive = Vector3.Lerp(prevRow.Emissive, nextRow.Emissive, tableRow.Weight); From 076dab924fa21e67d28e88ee5a01601c532160a5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 27 Jan 2024 18:58:26 +0100 Subject: [PATCH 5/6] Reuse same list for warnings and exceptions. --- .../ModEditWindow.Models.MdlTab.cs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs index b3c0cacd..37b9dfb5 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs @@ -28,8 +28,8 @@ public partial class ModEditWindow private bool _dirty; public bool PendingIo { get; private set; } - public List IoExceptions { get; private set; } = []; - public List IoWarnings { get; private set; } = []; + public List IoExceptions { get; } = []; + public List IoWarnings { get; } = []; public MdlTab(ModEditWindow edit, byte[] bytes, string path) { @@ -92,10 +92,7 @@ public partial class ModEditWindow .ToList(); }); - task.ContinueWith(t => - { - GamePaths = FinalizeIo(task); - }); + task.ContinueWith(t => { GamePaths = FinalizeIo(task); }); } private EstManipulation[] GetCurrentEstManipulations() @@ -246,8 +243,8 @@ public partial class ModEditWindow private void BeginIo() { PendingIo = true; - IoWarnings = []; - IoExceptions = []; + IoWarnings.Clear(); + IoExceptions.Clear(); } private void FinalizeIo(Task task) @@ -264,8 +261,9 @@ public partial class ModEditWindow { result = getResult(task.Result); if (getNotifier != null) - IoWarnings = getNotifier(task.Result).GetWarnings().ToList(); + IoWarnings.AddRange(getNotifier(task.Result).GetWarnings()); } + PendingIo = false; return result; @@ -273,12 +271,16 @@ public partial class ModEditWindow private void RecordIoExceptions(Exception? exception) { - IoExceptions = exception switch + switch (exception) { - null => [], - AggregateException ae => [.. ae.Flatten().InnerExceptions], - _ => [exception], - }; + case null: break; + case AggregateException ae: + IoExceptions.AddRange(ae.Flatten().InnerExceptions); + break; + default: + IoExceptions.Add(exception); + break; + } } /// Read a file from the active collection or game. From 1e4570bd79193e22d6870e1d057888d7f9b1653a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 27 Jan 2024 19:05:05 +0100 Subject: [PATCH 6/6] Slight cleanup. --- Penumbra/Import/Models/Export/MaterialExporter.cs | 2 +- Penumbra/Import/Models/Export/MeshExporter.cs | 2 +- Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs index cb2cf6f5..f17fdaa2 100644 --- a/Penumbra/Import/Models/Export/MaterialExporter.cs +++ b/Penumbra/Import/Models/Export/MaterialExporter.cs @@ -144,7 +144,7 @@ public class MaterialExporter // Specular (table) var lerpedSpecularColor = Vector3.Lerp(prevRow.Specular, nextRow.Specular, tableRow.Weight); - // float.Lerp is .NET8 ;-; + // float.Lerp is .NET8 ;-; #TODO var lerpedSpecularFactor = prevRow.SpecularStrength * (1.0f - tableRow.Weight) + nextRow.SpecularStrength * tableRow.Weight; specularSpan[x].FromVector4(new Vector4(lerpedSpecularColor, lerpedSpecularFactor)); diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index 1a06acd1..df315094 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -241,7 +241,7 @@ public class MeshExporter } else { - _notifier.Warning($"Invalid attribute data, ignoring."); + _notifier.Warning("Invalid attribute data, ignoring."); } return new MeshData diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs index 37b9dfb5..7adc4379 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs @@ -92,7 +92,7 @@ public partial class ModEditWindow .ToList(); }); - task.ContinueWith(t => { GamePaths = FinalizeIo(task); }); + task.ContinueWith(t => { GamePaths = FinalizeIo(t); }); } private EstManipulation[] GetCurrentEstManipulations() @@ -171,7 +171,7 @@ public partial class ModEditWindow /// Merge material configuration from the source onto the target. /// Model that will be updated. /// Model to copy material configuration from. - public void MergeMaterials(MdlFile target, MdlFile source) + private static void MergeMaterials(MdlFile target, MdlFile source) { target.Materials = source.Materials; @@ -186,7 +186,7 @@ public partial class ModEditWindow /// Merge attribute configuration from the source onto the target. /// Model that will be updated. > /// Model to copy attribute configuration from. - public static void MergeAttributes(MdlFile target, MdlFile source) + private static void MergeAttributes(MdlFile target, MdlFile source) { target.Attributes = source.Attributes; @@ -248,7 +248,7 @@ public partial class ModEditWindow } private void FinalizeIo(Task task) - => FinalizeIo(task, notifier => null, notifier => notifier); + => FinalizeIo(task, _ => null, notifier => notifier); private TResult? FinalizeIo(Task task) => FinalizeIo(task, result => result, null);