Merge branch 'mdl-io-triage-4'

This commit is contained in:
Ottermandias 2024-01-27 19:05:14 +01:00
commit 8a0d217977
4 changed files with 79 additions and 45 deletions

View file

@ -89,11 +89,15 @@ public class MaterialExporter
// TODO: handle other textures stored in the mask? // 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) return BuildSharedBase(material, name)
.WithBaseColor(BuildImage(baseColor, name, "basecolor")) .WithBaseColor(BuildImage(baseColor, name, "basecolor"))
.WithNormal(BuildImage(operation.Normal, name, "normal")) .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. // 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<Rgba32> Normal { get; } = normal.Clone(); public Image<Rgba32> Normal { get; } = normal.Clone();
public Image<Rgba32> BaseColor { get; } = new(normal.Width, normal.Height); public Image<Rgba32> BaseColor { get; } = new(normal.Width, normal.Height);
public Image<Rgb24> Specular { get; } = new(normal.Width, normal.Height); public Image<Rgba32> Specular { get; } = new(normal.Width, normal.Height);
public Image<Rgb24> Emissive { get; } = new(normal.Width, normal.Height); public Image<Rgb24> Emissive { get; } = new(normal.Width, normal.Height);
private Buffer2D<Rgba32> NormalBuffer private Buffer2D<Rgba32> NormalBuffer
@ -111,7 +115,7 @@ public class MaterialExporter
private Buffer2D<Rgba32> BaseColorBuffer private Buffer2D<Rgba32> BaseColorBuffer
=> BaseColor.Frames.RootFrame.PixelBuffer; => BaseColor.Frames.RootFrame.PixelBuffer;
private Buffer2D<Rgb24> SpecularBuffer private Buffer2D<Rgba32> SpecularBuffer
=> Specular.Frames.RootFrame.PixelBuffer; => Specular.Frames.RootFrame.PixelBuffer;
private Buffer2D<Rgb24> EmissiveBuffer private Buffer2D<Rgb24> EmissiveBuffer
@ -140,7 +144,9 @@ public class MaterialExporter
// Specular (table) // Specular (table)
var lerpedSpecularColor = Vector3.Lerp(prevRow.Specular, nextRow.Specular, tableRow.Weight); var lerpedSpecularColor = Vector3.Lerp(prevRow.Specular, nextRow.Specular, tableRow.Weight);
specularSpan[x].FromVector4(new Vector4(lerpedSpecularColor, 1)); // float.Lerp is .NET8 ;-; #TODO
var lerpedSpecularFactor = prevRow.SpecularStrength * (1.0f - tableRow.Weight) + nextRow.SpecularStrength * tableRow.Weight;
specularSpan[x].FromVector4(new Vector4(lerpedSpecularColor, lerpedSpecularFactor));
// Emissive (table) // Emissive (table)
var lerpedEmissive = Vector3.Lerp(prevRow.Emissive, nextRow.Emissive, tableRow.Weight); var lerpedEmissive = Vector3.Lerp(prevRow.Emissive, nextRow.Emissive, tableRow.Weight);

View file

@ -230,10 +230,19 @@ public class MeshExporter
{ "targetNames", shapeNames }, { "targetNames", shapeNames },
}); });
var attributes = Enumerable.Range(0, 32) string[] attributes = [];
.Where(index => ((attributeMask >> index) & 1) == 1) var maxAttribute = 31 - BitOperations.LeadingZeroCount(attributeMask);
.Select(index => _mdl.Attributes[index]) if (maxAttribute < _mdl.Attributes.Length)
.ToArray(); {
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 return new MeshData
{ {

View file

@ -11,7 +11,8 @@ and there's reason to overhaul the export pipeline.
public struct VertexColorFfxiv : IVertexCustom 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 Vector4 FfxivColor;
public int MaxColors => 0; public int MaxColors => 0;
@ -80,7 +81,7 @@ public struct VertexTexture1ColorFfxiv : IVertexCustom
[VertexAttribute("TEXCOORD_0")] [VertexAttribute("TEXCOORD_0")]
public Vector2 TexCoord0; public Vector2 TexCoord0;
[VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)] [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, false)]
public Vector4 FfxivColor; public Vector4 FfxivColor;
public int MaxColors => 0; public int MaxColors => 0;
@ -162,7 +163,7 @@ public struct VertexTexture2ColorFfxiv : IVertexCustom
[VertexAttribute("TEXCOORD_1")] [VertexAttribute("TEXCOORD_1")]
public Vector2 TexCoord1; public Vector2 TexCoord1;
[VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)] [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, false)]
public Vector4 FfxivColor; public Vector4 FfxivColor;
public int MaxColors => 0; public int MaxColors => 0;

View file

@ -2,6 +2,7 @@ using Lumina.Data.Parsing;
using OtterGui; using OtterGui;
using Penumbra.GameData; using Penumbra.GameData;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.Import.Models;
using Penumbra.Import.Models.Export; using Penumbra.Import.Models.Export;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.String.Classes; using Penumbra.String.Classes;
@ -27,8 +28,8 @@ public partial class ModEditWindow
private bool _dirty; private bool _dirty;
public bool PendingIo { get; private set; } public bool PendingIo { get; private set; }
public List<Exception> IoExceptions { get; private set; } = []; public List<Exception> IoExceptions { get; } = [];
public List<string> IoWarnings { get; private set; } = []; public List<string> IoWarnings { get; } = [];
public MdlTab(ModEditWindow edit, byte[] bytes, string path) public MdlTab(ModEditWindow edit, byte[] bytes, string path)
{ {
@ -79,7 +80,7 @@ public partial class ModEditWindow
return; return;
} }
PendingIo = true; BeginIo();
var task = Task.Run(() => 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? // TODO: Is it worth trying to order results based on option priorities for cases where more than one match is found?
@ -91,12 +92,7 @@ public partial class ModEditWindow
.ToList(); .ToList();
}); });
task.ContinueWith(t => task.ContinueWith(t => { GamePaths = FinalizeIo(t); });
{
RecordIoExceptions(t.Exception);
GamePaths = t.Result;
PendingIo = false;
});
} }
private EstManipulation[] GetCurrentEstManipulations() private EstManipulation[] GetCurrentEstManipulations()
@ -132,33 +128,22 @@ public partial class ModEditWindow
return; return;
} }
PendingIo = true; BeginIo();
_edit._models.ExportToGltf(ExportConfig, Mdl, sklbPaths, ReadFile, outputPath) _edit._models.ExportToGltf(ExportConfig, Mdl, sklbPaths, ReadFile, outputPath)
.ContinueWith(task => .ContinueWith(FinalizeIo);
{
RecordIoExceptions(task.Exception);
if (task is { IsCompletedSuccessfully: true, Result: not null })
IoWarnings = task.Result.GetWarnings().ToList();
PendingIo = false;
});
} }
/// <summary> Import a model from an interchange format. </summary> /// <summary> Import a model from an interchange format. </summary>
/// <param name="inputPath"> Disk path to load model data from. </param> /// <param name="inputPath"> Disk path to load model data from. </param>
public void Import(string inputPath) public void Import(string inputPath)
{ {
PendingIo = true; BeginIo();
_edit._models.ImportGltf(inputPath) _edit._models.ImportGltf(inputPath)
.ContinueWith(task => .ContinueWith(task =>
{ {
RecordIoExceptions(task.Exception); var mdlFile = FinalizeIo(task, result => result.Item1, result => result.Item2);
if (task is { IsCompletedSuccessfully: true, Result: (not null, _) }) if (mdlFile != null)
{ FinalizeImport(mdlFile);
IoWarnings = task.Result.Item2.GetWarnings().ToList();
FinalizeImport(task.Result.Item1);
}
PendingIo = false;
}); });
} }
@ -186,7 +171,7 @@ public partial class ModEditWindow
/// <summary> Merge material configuration from the source onto the target. </summary> /// <summary> Merge material configuration from the source onto the target. </summary>
/// <param name="target"> Model that will be updated. </param> /// <param name="target"> Model that will be updated. </param>
/// <param name="source"> Model to copy material configuration from. </param> /// <param name="source"> Model to copy material configuration from. </param>
public void MergeMaterials(MdlFile target, MdlFile source) private static void MergeMaterials(MdlFile target, MdlFile source)
{ {
target.Materials = source.Materials; target.Materials = source.Materials;
@ -201,7 +186,7 @@ public partial class ModEditWindow
/// <summary> Merge attribute configuration from the source onto the target. </summary> /// <summary> Merge attribute configuration from the source onto the target. </summary>
/// <param name="target"> Model that will be updated. ></param> /// <param name="target"> Model that will be updated. ></param>
/// <param name="source"> Model to copy attribute configuration from. </param> /// <param name="source"> Model to copy attribute configuration from. </param>
public static void MergeAttributes(MdlFile target, MdlFile source) private static void MergeAttributes(MdlFile target, MdlFile source)
{ {
target.Attributes = source.Attributes; target.Attributes = source.Attributes;
@ -255,14 +240,47 @@ public partial class ModEditWindow
target.ElementIds = [.. elementIds]; target.ElementIds = [.. elementIds];
} }
private void BeginIo()
{
PendingIo = true;
IoWarnings.Clear();
IoExceptions.Clear();
}
private void FinalizeIo(Task<IoNotifier> task)
=> FinalizeIo<IoNotifier, object?>(task, _ => null, notifier => notifier);
private TResult? FinalizeIo<TResult>(Task<TResult> task)
=> FinalizeIo(task, result => result, null);
private TResult? FinalizeIo<TTask, TResult>(Task<TTask> task, Func<TTask, TResult> getResult, Func<TTask, IoNotifier>? getNotifier)
{
TResult? result = default;
RecordIoExceptions(task.Exception);
if (task is { IsCompletedSuccessfully: true, Result: not null })
{
result = getResult(task.Result);
if (getNotifier != null)
IoWarnings.AddRange(getNotifier(task.Result).GetWarnings());
}
PendingIo = false;
return result;
}
private void RecordIoExceptions(Exception? exception) private void RecordIoExceptions(Exception? exception)
{ {
IoExceptions = exception switch switch (exception)
{ {
null => [], case null: break;
AggregateException ae => [.. ae.Flatten().InnerExceptions], case AggregateException ae:
_ => [exception], IoExceptions.AddRange(ae.Flatten().InnerExceptions);
}; break;
default:
IoExceptions.Add(exception);
break;
}
} }
/// <summary> Read a file from the active collection or game. </summary> /// <summary> Read a file from the active collection or game. </summary>