Skip to content

Commit 8a0d217

Browse files
committed
Merge branch 'mdl-io-triage-4'
2 parents 5a80e65 + 1e4570b commit 8a0d217

File tree

4 files changed

+79
-45
lines changed

4 files changed

+79
-45
lines changed

Penumbra/Import/Models/Export/MaterialExporter.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,15 @@ private static MaterialBuilder BuildCharacter(Material material, string name)
8989
// TODO: handle other textures stored in the mask?
9090
}
9191

92+
// Specular extension puts colour on RGB and factor on A. We're already packing like that, so we can reuse the texture.
93+
var specularImage = BuildImage(specular, name, "specular");
94+
9295
return BuildSharedBase(material, name)
9396
.WithBaseColor(BuildImage(baseColor, name, "basecolor"))
9497
.WithNormal(BuildImage(operation.Normal, name, "normal"))
95-
.WithSpecularColor(BuildImage(specular, name, "specular"))
96-
.WithEmissive(BuildImage(operation.Emissive, name, "emissive"), Vector3.One, 1);
98+
.WithEmissive(BuildImage(operation.Emissive, name, "emissive"), Vector3.One, 1)
99+
.WithSpecularFactor(specularImage, 1)
100+
.WithSpecularColor(specularImage);
97101
}
98102

99103
// 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 @@ private readonly struct ProcessCharacterNormalOperation(Image<Rgba32> normal, Mt
102106
{
103107
public Image<Rgba32> Normal { get; } = normal.Clone();
104108
public Image<Rgba32> BaseColor { get; } = new(normal.Width, normal.Height);
105-
public Image<Rgb24> Specular { get; } = new(normal.Width, normal.Height);
109+
public Image<Rgba32> Specular { get; } = new(normal.Width, normal.Height);
106110
public Image<Rgb24> Emissive { get; } = new(normal.Width, normal.Height);
107111

108112
private Buffer2D<Rgba32> NormalBuffer
@@ -111,7 +115,7 @@ private Buffer2D<Rgba32> NormalBuffer
111115
private Buffer2D<Rgba32> BaseColorBuffer
112116
=> BaseColor.Frames.RootFrame.PixelBuffer;
113117

114-
private Buffer2D<Rgb24> SpecularBuffer
118+
private Buffer2D<Rgba32> SpecularBuffer
115119
=> Specular.Frames.RootFrame.PixelBuffer;
116120

117121
private Buffer2D<Rgb24> EmissiveBuffer
@@ -140,7 +144,9 @@ public void Invoke(int y)
140144

141145
// Specular (table)
142146
var lerpedSpecularColor = Vector3.Lerp(prevRow.Specular, nextRow.Specular, tableRow.Weight);
143-
specularSpan[x].FromVector4(new Vector4(lerpedSpecularColor, 1));
147+
// float.Lerp is .NET8 ;-; #TODO
148+
var lerpedSpecularFactor = prevRow.SpecularStrength * (1.0f - tableRow.Weight) + nextRow.SpecularStrength * tableRow.Weight;
149+
specularSpan[x].FromVector4(new Vector4(lerpedSpecularColor, lerpedSpecularFactor));
144150

145151
// Emissive (table)
146152
var lerpedEmissive = Vector3.Lerp(prevRow.Emissive, nextRow.Emissive, tableRow.Weight);

Penumbra/Import/Models/Export/MeshExporter.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,19 @@ uint attributeMask
230230
{ "targetNames", shapeNames },
231231
});
232232

233-
var attributes = Enumerable.Range(0, 32)
234-
.Where(index => ((attributeMask >> index) & 1) == 1)
235-
.Select(index => _mdl.Attributes[index])
236-
.ToArray();
233+
string[] attributes = [];
234+
var maxAttribute = 31 - BitOperations.LeadingZeroCount(attributeMask);
235+
if (maxAttribute < _mdl.Attributes.Length)
236+
{
237+
attributes = Enumerable.Range(0, 32)
238+
.Where(index => ((attributeMask >> index) & 1) == 1)
239+
.Select(index => _mdl.Attributes[index])
240+
.ToArray();
241+
}
242+
else
243+
{
244+
_notifier.Warning("Invalid attribute data, ignoring.");
245+
}
237246

238247
return new MeshData
239248
{

Penumbra/Import/Models/Export/VertexFragment.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ and there's reason to overhaul the export pipeline.
1111

1212
public struct VertexColorFfxiv : IVertexCustom
1313
{
14-
[VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)]
14+
// NOTE: We only realistically require UNSIGNED_BYTE for this, however Blender 3.6 errors on that (fixed in 4.0).
15+
[VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, false)]
1516
public Vector4 FfxivColor;
1617

1718
public int MaxColors => 0;
@@ -80,7 +81,7 @@ public struct VertexTexture1ColorFfxiv : IVertexCustom
8081
[VertexAttribute("TEXCOORD_0")]
8182
public Vector2 TexCoord0;
8283

83-
[VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)]
84+
[VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, false)]
8485
public Vector4 FfxivColor;
8586

8687
public int MaxColors => 0;
@@ -162,7 +163,7 @@ public struct VertexTexture2ColorFfxiv : IVertexCustom
162163
[VertexAttribute("TEXCOORD_1")]
163164
public Vector2 TexCoord1;
164165

165-
[VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)]
166+
[VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, false)]
166167
public Vector4 FfxivColor;
167168

168169
public int MaxColors => 0;

Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using OtterGui;
33
using Penumbra.GameData;
44
using Penumbra.GameData.Files;
5+
using Penumbra.Import.Models;
56
using Penumbra.Import.Models.Export;
67
using Penumbra.Meta.Manipulations;
78
using Penumbra.String.Classes;
@@ -27,8 +28,8 @@ private class MdlTab : IWritable
2728

2829
private bool _dirty;
2930
public bool PendingIo { get; private set; }
30-
public List<Exception> IoExceptions { get; private set; } = [];
31-
public List<string> IoWarnings { get; private set; } = [];
31+
public List<Exception> IoExceptions { get; } = [];
32+
public List<string> IoWarnings { get; } = [];
3233

3334
public MdlTab(ModEditWindow edit, byte[] bytes, string path)
3435
{
@@ -79,7 +80,7 @@ private void FindGamePaths(string path)
7980
return;
8081
}
8182

82-
PendingIo = true;
83+
BeginIo();
8384
var task = Task.Run(() =>
8485
{
8586
// 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 @@ private void FindGamePaths(string path)
9192
.ToList();
9293
});
9394

94-
task.ContinueWith(t =>
95-
{
96-
RecordIoExceptions(t.Exception);
97-
GamePaths = t.Result;
98-
PendingIo = false;
99-
});
95+
task.ContinueWith(t => { GamePaths = FinalizeIo(t); });
10096
}
10197

10298
private EstManipulation[] GetCurrentEstManipulations()
@@ -132,33 +128,22 @@ public void Export(string outputPath, Utf8GamePath mdlPath)
132128
return;
133129
}
134130

135-
PendingIo = true;
131+
BeginIo();
136132
_edit._models.ExportToGltf(ExportConfig, Mdl, sklbPaths, ReadFile, outputPath)
137-
.ContinueWith(task =>
138-
{
139-
RecordIoExceptions(task.Exception);
140-
if (task is { IsCompletedSuccessfully: true, Result: not null })
141-
IoWarnings = task.Result.GetWarnings().ToList();
142-
PendingIo = false;
143-
});
133+
.ContinueWith(FinalizeIo);
144134
}
145135

146136
/// <summary> Import a model from an interchange format. </summary>
147137
/// <param name="inputPath"> Disk path to load model data from. </param>
148138
public void Import(string inputPath)
149139
{
150-
PendingIo = true;
140+
BeginIo();
151141
_edit._models.ImportGltf(inputPath)
152142
.ContinueWith(task =>
153143
{
154-
RecordIoExceptions(task.Exception);
155-
if (task is { IsCompletedSuccessfully: true, Result: (not null, _) })
156-
{
157-
IoWarnings = task.Result.Item2.GetWarnings().ToList();
158-
FinalizeImport(task.Result.Item1);
159-
}
160-
161-
PendingIo = false;
144+
var mdlFile = FinalizeIo(task, result => result.Item1, result => result.Item2);
145+
if (mdlFile != null)
146+
FinalizeImport(mdlFile);
162147
});
163148
}
164149

@@ -186,7 +171,7 @@ private void FinalizeImport(MdlFile newMdl)
186171
/// <summary> Merge material configuration from the source onto the target. </summary>
187172
/// <param name="target"> Model that will be updated. </param>
188173
/// <param name="source"> Model to copy material configuration from. </param>
189-
public void MergeMaterials(MdlFile target, MdlFile source)
174+
private static void MergeMaterials(MdlFile target, MdlFile source)
190175
{
191176
target.Materials = source.Materials;
192177

@@ -201,7 +186,7 @@ public void MergeMaterials(MdlFile target, MdlFile source)
201186
/// <summary> Merge attribute configuration from the source onto the target. </summary>
202187
/// <param name="target"> Model that will be updated. ></param>
203188
/// <param name="source"> Model to copy attribute configuration from. </param>
204-
public static void MergeAttributes(MdlFile target, MdlFile source)
189+
private static void MergeAttributes(MdlFile target, MdlFile source)
205190
{
206191
target.Attributes = source.Attributes;
207192

@@ -255,14 +240,47 @@ private static void MergeElementIds(MdlFile target, MdlFile source)
255240
target.ElementIds = [.. elementIds];
256241
}
257242

243+
private void BeginIo()
244+
{
245+
PendingIo = true;
246+
IoWarnings.Clear();
247+
IoExceptions.Clear();
248+
}
249+
250+
private void FinalizeIo(Task<IoNotifier> task)
251+
=> FinalizeIo<IoNotifier, object?>(task, _ => null, notifier => notifier);
252+
253+
private TResult? FinalizeIo<TResult>(Task<TResult> task)
254+
=> FinalizeIo(task, result => result, null);
255+
256+
private TResult? FinalizeIo<TTask, TResult>(Task<TTask> task, Func<TTask, TResult> getResult, Func<TTask, IoNotifier>? getNotifier)
257+
{
258+
TResult? result = default;
259+
RecordIoExceptions(task.Exception);
260+
if (task is { IsCompletedSuccessfully: true, Result: not null })
261+
{
262+
result = getResult(task.Result);
263+
if (getNotifier != null)
264+
IoWarnings.AddRange(getNotifier(task.Result).GetWarnings());
265+
}
266+
267+
PendingIo = false;
268+
269+
return result;
270+
}
271+
258272
private void RecordIoExceptions(Exception? exception)
259273
{
260-
IoExceptions = exception switch
274+
switch (exception)
261275
{
262-
null => [],
263-
AggregateException ae => [.. ae.Flatten().InnerExceptions],
264-
_ => [exception],
265-
};
276+
case null: break;
277+
case AggregateException ae:
278+
IoExceptions.AddRange(ae.Flatten().InnerExceptions);
279+
break;
280+
default:
281+
IoExceptions.Add(exception);
282+
break;
283+
}
266284
}
267285

268286
/// <summary> Read a file from the active collection or game. </summary>

0 commit comments

Comments
 (0)