Cleanup pass

This commit is contained in:
ackwell 2024-01-14 20:12:17 +11:00
parent 5e6ca8b22c
commit 9ff3227cf4
3 changed files with 30 additions and 16 deletions

View file

@ -21,6 +21,7 @@ public class MaterialExporter
// variant?
}
/// <summary> Build a glTF material from a hydrated XIV model, with the provided name. </summary>
public static MaterialBuilder Export(Material material, string name)
{
Penumbra.Log.Debug($"Exporting material \"{name}\".");
@ -36,16 +37,18 @@ public class MaterialExporter
};
}
/// <summary> Build a material following the semantics of character.shpk. </summary>
private static MaterialBuilder BuildCharacter(Material material, string name)
{
// Build the textures from the color table.
var table = material.Mtrl.Table;
// TODO: there's a few normal usages i should check, i think.
var normal = material.Textures[TextureUsage.SamplerNormal];
var operation = new ProcessCharacterNormalOperation(normal, table);
ParallelRowIterator.IterateRows(ImageSharpConfiguration.Default, normal.Bounds(), in operation);
// Check if full textures are provided, and merge in if available.
Image baseColor = operation.BaseColor;
if (material.Textures.TryGetValue(TextureUsage.SamplerDiffuse, out var diffuse))
{
@ -53,7 +56,6 @@ public class MaterialExporter
baseColor = diffuse;
}
// TODO: what about the two specularmaps?
Image specular = operation.Specular;
if (material.Textures.TryGetValue(TextureUsage.SamplerSpecular, out var specularTexture))
{
@ -61,6 +63,7 @@ public class MaterialExporter
specular = specularTexture;
}
// Pull further information from the mask.
Image? occlusion = null;
if (material.Textures.TryGetValue(TextureUsage.SamplerMask, out var maskTexture))
{
@ -73,7 +76,7 @@ public class MaterialExporter
0f, 0f, 0f, 0f
)));
occlusion = maskTexture;
// TODO: handle other textures stored in the mask?
}
@ -89,7 +92,7 @@ public class MaterialExporter
return materialBuilder;
}
// TODO: It feels a little silly to request the entire normal here when extrating 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.
// As a future refactor, it would be neat to accept a single-channel field here, and then do composition of other stuff later.
private readonly struct ProcessCharacterNormalOperation(Image<Rgba32> normal, MtrlFile.ColorTable table) : IRowOperation
{
@ -143,7 +146,7 @@ public class MaterialExporter
private static TableRow GetTableRowIndices(float input)
{
// These calculations are ported from character.shpk.
var smoothed = MathF.Floor(((input * 7.5f) % 1.0f) * 2)
var smoothed = MathF.Floor(((input * 7.5f) % 1.0f) * 2)
* (-input * 15 + MathF.Floor(input * 15 + 0.5f))
+ input * 15;
@ -204,6 +207,7 @@ public class MaterialExporter
private static Vector4 _defaultHairColor = new Vector4(130, 64, 13, 255) / new Vector4(255);
private static Vector4 _defaultHighlightColor = new Vector4(77, 126, 240, 255) / new Vector4(255);
/// <summary> Build a material following the semantics of hair.shpk. </summary>
private static MaterialBuilder BuildHair(Material material, string name)
{
// Trust me bro.
@ -241,11 +245,12 @@ public class MaterialExporter
return BuildSharedBase(material, name)
.WithBaseColor(BuildImage(baseColor, name, "basecolor"))
.WithNormal(BuildImage(normal, name, "normal"))
.WithAlpha(isFace? AlphaMode.BLEND : AlphaMode.MASK, 0.5f);
.WithAlpha(isFace ? AlphaMode.BLEND : AlphaMode.MASK, 0.5f);
}
private static Vector4 _defaultEyeColor = new Vector4(21, 176, 172, 255) / new Vector4(255);
/// <summary> Build a material following the semantics of iris.shpk. </summary>
// NOTE: This is largely the same as the hair material, but is also missing a few features that would cause it to diverge. Keeping seperate for now.
private static MaterialBuilder BuildIris(Material material, string name)
{
@ -278,6 +283,7 @@ public class MaterialExporter
.WithNormal(BuildImage(normal, name, "normal"));
}
/// <summary> Build a material following the semantics of skin.shpk. </summary>
private static MaterialBuilder BuildSkin(Material material, string name)
{
// Trust me bro.
@ -310,7 +316,8 @@ public class MaterialExporter
});
// Clear the blue channel out of the normal now that we're done with it.
normal.ProcessPixelRows(normalAccessor => {
normal.ProcessPixelRows(normalAccessor =>
{
for (int y = 0; y < normalAccessor.Height; y++)
{
var normalSpan = normalAccessor.GetRowSpan(y);
@ -325,14 +332,16 @@ public class MaterialExporter
return BuildSharedBase(material, name)
.WithBaseColor(BuildImage(diffuse, name, "basecolor"))
.WithNormal(BuildImage(normal, name, "normal"))
.WithAlpha(isFace? AlphaMode.MASK : AlphaMode.OPAQUE, 0.5f);
.WithAlpha(isFace ? AlphaMode.MASK : AlphaMode.OPAQUE, 0.5f);
}
/// <summary> Build a material from a source with unknown semantics. </summary>
/// <remarks> Will make a loose effort to fetch common / simple textures. </remarks>
private static MaterialBuilder BuildFallback(Material material, string name)
{
Penumbra.Log.Warning($"Unhandled shader package: {material.Mtrl.ShaderPackage.Name}");
var materialBuilder = BuildSharedBase(material, name)
var materialBuilder = BuildSharedBase(material, name)
.WithMetallicRoughnessShader()
.WithBaseColor(Vector4.One);
@ -345,6 +354,7 @@ public class MaterialExporter
return materialBuilder;
}
/// <summary> Build a material pre-configured with settings common to all XIV materials/shaders. </summary>
private static MaterialBuilder BuildSharedBase(Material material, string name)
{
// TODO: Move this and potentially the other known stuff into MtrlFile?
@ -355,6 +365,7 @@ public class MaterialExporter
.WithDoubleSide(showBackfaces);
}
/// <summary> Convert an ImageSharp Image into an ImageBuilder for use with SharpGLTF. </summary>
private static ImageBuilder BuildImage(Image image, string materialName, string suffix)
{
var name = materialName.Replace("/", "").Replace(".mtrl", "") + $"_{suffix}";

View file

@ -23,8 +23,8 @@ using LuminaMaterial = Lumina.Models.Materials.Material;
public sealed class ModelManager(IFramework framework, ActiveCollections collections, IDataManager gameData, GamePathParser parser, TextureManager textureManager) : SingleTaskQueue, IDisposable
{
private readonly IFramework _framework = framework;
private readonly IDataManager _gameData = gameData;
private readonly IFramework _framework = framework;
private readonly IDataManager _gameData = gameData;
private readonly TextureManager _textureManager = textureManager;
private readonly ConcurrentDictionary<IAction, (Task, CancellationTokenSource)> _tasks = new();
@ -163,7 +163,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
Penumbra.Log.Debug("[GLTF Export] Done.");
}
/// <summary> Attempt to read out the pertinent information from a .sklb. </summary>
/// <summary> Attempt to read out the pertinent information from the sklb file paths provided. </summary>
private IEnumerable<XivSkeleton> BuildSkeletons(CancellationToken cancel)
{
var havokTasks = sklbPaths
@ -185,6 +185,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
delayTicks: pair.Index, cancellationToken: cancel);
}
/// <summary> Read a .mtrl and hydrate its textures. </summary>
private MaterialExporter.Material BuildMaterial(string relativePath, CancellationToken cancel)
{
// TODO: this should probably be chosen in the export settings
@ -194,7 +195,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
? LuminaMaterial.ResolveRelativeMaterialPath(relativePath, variantId)
: relativePath;
// TODO: this should be a recoverable warning - as should the one below it i think
// TODO: this should be a recoverable warning
if (absolutePath == null)
throw new Exception("Failed to resolve material path.");
@ -202,7 +203,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
return new MaterialExporter.Material
{
Mtrl = mtrl,
Mtrl = mtrl,
Textures = mtrl.ShaderPackage.Samplers.ToDictionary(
sampler => (TextureUsage)sampler.SamplerId,
sampler => ConvertImage(mtrl.Textures[sampler.TextureIndex], cancel)
@ -210,6 +211,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
};
}
/// <summary> Read a texture referenced by a .mtrl and convert it into an ImageSharp image. </summary>
private Image<Rgba32> ConvertImage(MtrlFile.Texture texture, CancellationToken cancel)
{
using var textureData = new MemoryStream(read(texture.Path));

View file

@ -225,15 +225,16 @@ public partial class ModEditWindow
private byte[] ReadFile(string path)
{
// TODO: if cross-collection lookups are turned off, this conversion can be skipped
if (!Utf8GamePath.FromString(path, out var utf8SklbPath, true))
if (!Utf8GamePath.FromString(path, out var utf8Path, true))
throw new Exception($"Resolved path {path} could not be converted to a game path.");
var resolvedPath = _edit._activeCollections.Current.ResolvePath(utf8SklbPath);
var resolvedPath = _edit._activeCollections.Current.ResolvePath(utf8Path);
// TODO: is it worth trying to use streams for these instead? I'll need to do this for mtrl/tex too, so might be a good idea. that said, the mtrl reader doesn't accept streams, so...
var bytes = resolvedPath == null
? _edit._gameData.GetFile(path)?.Data
: File.ReadAllBytes(resolvedPath.Value.ToPath());
// TODO: some callers may not care about failures - handle exceptions seperately?
return bytes ?? throw new Exception(
$"Resolved path {path} could not be found. If modded, is it enabled in the current collection?");
}