diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index 928c8670..1c266e52 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -373,11 +373,11 @@ public class MeshExporter return materialUsages switch { - (2, true) => typeof(VertexColor1Texture2), + (2, true) => typeof(VertexTexture2ColorFfxiv), (2, false) => typeof(VertexTexture2), - (1, true) => typeof(VertexColor1Texture1), + (1, true) => typeof(VertexTexture1ColorFfxiv), (1, false) => typeof(VertexTexture1), - (0, true) => typeof(VertexColor1), + (0, true) => typeof(VertexColorFfxiv), (0, false) => typeof(VertexEmpty), _ => throw new Exception("Unreachable."), @@ -390,16 +390,16 @@ public class MeshExporter if (_materialType == typeof(VertexEmpty)) return new VertexEmpty(); - if (_materialType == typeof(VertexColor1)) - return new VertexColor1(ToVector4(attributes[MdlFile.VertexUsage.Color])); + if (_materialType == typeof(VertexColorFfxiv)) + return new VertexColorFfxiv(ToVector4(attributes[MdlFile.VertexUsage.Color])); if (_materialType == typeof(VertexTexture1)) return new VertexTexture1(ToVector2(attributes[MdlFile.VertexUsage.UV])); - if (_materialType == typeof(VertexColor1Texture1)) - return new VertexColor1Texture1( - ToVector4(attributes[MdlFile.VertexUsage.Color]), - ToVector2(attributes[MdlFile.VertexUsage.UV]) + if (_materialType == typeof(VertexTexture1ColorFfxiv)) + return new VertexTexture1ColorFfxiv( + ToVector2(attributes[MdlFile.VertexUsage.UV]), + ToVector4(attributes[MdlFile.VertexUsage.Color]) ); // XIV packs two UVs into a single vec4 attribute. @@ -413,13 +413,13 @@ public class MeshExporter ); } - if (_materialType == typeof(VertexColor1Texture2)) + if (_materialType == typeof(VertexTexture2ColorFfxiv)) { var uv = ToVector4(attributes[MdlFile.VertexUsage.UV]); - return new VertexColor1Texture2( - ToVector4(attributes[MdlFile.VertexUsage.Color]), + return new VertexTexture2ColorFfxiv( new Vector2(uv.X, uv.Y), - new Vector2(uv.Z, uv.W) + new Vector2(uv.Z, uv.W), + ToVector4(attributes[MdlFile.VertexUsage.Color]) ); } diff --git a/Penumbra/Import/Models/Export/VertexFragment.cs b/Penumbra/Import/Models/Export/VertexFragment.cs new file mode 100644 index 00000000..27d2ab10 --- /dev/null +++ b/Penumbra/Import/Models/Export/VertexFragment.cs @@ -0,0 +1,241 @@ +using SharpGLTF.Geometry.VertexTypes; +using SharpGLTF.Schema2; + +namespace Penumbra.Import.Models.Export; + +/* +Yeah, look, I tried to make this file less garbage. It's a little difficult. +Realistically, it will need to stick around until transforms/mutations are built +and there's reason to overhaul the export pipeline. +*/ + +public struct VertexColorFfxiv : IVertexCustom +{ + [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)] + public Vector4 FfxivColor; + + public int MaxColors => 0; + + public int MaxTextCoords => 0; + + private static readonly string[] CustomNames = ["_FFXIV_COLOR"]; + public IEnumerable CustomAttributes => CustomNames; + + public VertexColorFfxiv(Vector4 ffxivColor) + { + FfxivColor = ffxivColor; + } + + public void Add(in VertexMaterialDelta delta) + { + } + + public VertexMaterialDelta Subtract(IVertexMaterial baseValue) + => new VertexMaterialDelta(Vector4.Zero, Vector4.Zero, Vector2.Zero, Vector2.Zero); + + public Vector2 GetTexCoord(int index) + => throw new ArgumentOutOfRangeException(nameof(index)); + + public void SetTexCoord(int setIndex, Vector2 coord) + { + } + + public bool TryGetCustomAttribute(string attributeName, out object? value) + { + switch (attributeName) + { + case "_FFXIV_COLOR": + value = FfxivColor; + return true; + + default: + value = null; + return false; + } + } + + public void SetCustomAttribute(string attributeName, object value) + { + if (attributeName == "_FFXIV_COLOR" && value is Vector4 valueVector4) + FfxivColor = valueVector4; + } + + public Vector4 GetColor(int index) + => throw new ArgumentOutOfRangeException(nameof(index)); + + public void SetColor(int setIndex, Vector4 color) + { + } + + public void Validate() + { + var components = new[] { FfxivColor.X, FfxivColor.Y, FfxivColor.Z, FfxivColor.W }; + if (components.Any(component => component < 0 || component > 1)) + throw new ArgumentOutOfRangeException(nameof(FfxivColor)); + } +} + +public struct VertexTexture1ColorFfxiv : IVertexCustom +{ + [VertexAttribute("TEXCOORD_0")] + public Vector2 TexCoord0; + + [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)] + public Vector4 FfxivColor; + + public int MaxColors => 0; + + public int MaxTextCoords => 1; + + private static readonly string[] CustomNames = ["_FFXIV_COLOR"]; + public IEnumerable CustomAttributes => CustomNames; + + public VertexTexture1ColorFfxiv(Vector2 texCoord0, Vector4 ffxivColor) + { + TexCoord0 = texCoord0; + FfxivColor = ffxivColor; + } + + public void Add(in VertexMaterialDelta delta) + { + TexCoord0 += delta.TexCoord0Delta; + } + + public VertexMaterialDelta Subtract(IVertexMaterial baseValue) + { + return new VertexMaterialDelta(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), Vector2.Zero); + } + + public Vector2 GetTexCoord(int index) + => index switch + { + 0 => TexCoord0, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + public void SetTexCoord(int setIndex, Vector2 coord) + { + if (setIndex == 0) TexCoord0 = coord; + if (setIndex >= 1) throw new ArgumentOutOfRangeException(nameof(setIndex)); + } + + public bool TryGetCustomAttribute(string attributeName, out object? value) + { + switch (attributeName) + { + case "_FFXIV_COLOR": + value = FfxivColor; + return true; + + default: + value = null; + return false; + } + } + + public void SetCustomAttribute(string attributeName, object value) + { + if (attributeName == "_FFXIV_COLOR" && value is Vector4 valueVector4) + FfxivColor = valueVector4; + } + + public Vector4 GetColor(int index) + => throw new ArgumentOutOfRangeException(nameof(index)); + + public void SetColor(int setIndex, Vector4 color) + { + } + + public void Validate() + { + var components = new[] { FfxivColor.X, FfxivColor.Y, FfxivColor.Z, FfxivColor.W }; + if (components.Any(component => component < 0 || component > 1)) + throw new ArgumentOutOfRangeException(nameof(FfxivColor)); + } +} + +public struct VertexTexture2ColorFfxiv : IVertexCustom +{ + [VertexAttribute("TEXCOORD_0")] + public Vector2 TexCoord0; + + [VertexAttribute("TEXCOORD_1")] + public Vector2 TexCoord1; + + [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_BYTE, false)] + public Vector4 FfxivColor; + + public int MaxColors => 0; + + public int MaxTextCoords => 2; + + private static readonly string[] CustomNames = ["_FFXIV_COLOR"]; + public IEnumerable CustomAttributes => CustomNames; + + public VertexTexture2ColorFfxiv(Vector2 texCoord0, Vector2 texCoord1, Vector4 ffxivColor) + { + TexCoord0 = texCoord0; + TexCoord1 = texCoord1; + FfxivColor = ffxivColor; + } + + public void Add(in VertexMaterialDelta delta) + { + TexCoord0 += delta.TexCoord0Delta; + TexCoord1 += delta.TexCoord1Delta; + } + + public VertexMaterialDelta Subtract(IVertexMaterial baseValue) + { + return new VertexMaterialDelta(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), TexCoord1 - baseValue.GetTexCoord(1)); + } + + public Vector2 GetTexCoord(int index) + => index switch + { + 0 => TexCoord0, + 1 => TexCoord1, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + public void SetTexCoord(int setIndex, Vector2 coord) + { + if (setIndex == 0) TexCoord0 = coord; + if (setIndex == 1) TexCoord1 = coord; + if (setIndex >= 2) throw new ArgumentOutOfRangeException(nameof(setIndex)); + } + + public bool TryGetCustomAttribute(string attributeName, out object? value) + { + switch (attributeName) + { + case "_FFXIV_COLOR": + value = FfxivColor; + return true; + + default: + value = null; + return false; + } + } + + public void SetCustomAttribute(string attributeName, object value) + { + if (attributeName == "_FFXIV_COLOR" && value is Vector4 valueVector4) + FfxivColor = valueVector4; + } + + public Vector4 GetColor(int index) + => throw new ArgumentOutOfRangeException(nameof(index)); + + public void SetColor(int setIndex, Vector4 color) + { + } + + public void Validate() + { + var components = new[] { FfxivColor.X, FfxivColor.Y, FfxivColor.Z, FfxivColor.W }; + if (components.Any(component => component < 0 || component > 1)) + throw new ArgumentOutOfRangeException(nameof(FfxivColor)); + } +} diff --git a/Penumbra/Import/Models/Import/VertexAttribute.cs b/Penumbra/Import/Models/Import/VertexAttribute.cs index b73f6a89..b8576108 100644 --- a/Penumbra/Import/Models/Import/VertexAttribute.cs +++ b/Penumbra/Import/Models/Import/VertexAttribute.cs @@ -395,7 +395,9 @@ public class VertexAttribute public static VertexAttribute Color(Accessors accessors) { - accessors.TryGetValue("COLOR_0", out var accessor); + // Try to retrieve the custom color attribute we use for export, falling back to the glTF standard name. + if (!accessors.TryGetValue("_FFXIV_COLOR", out var accessor)) + accessors.TryGetValue("COLOR_0", out accessor); var element = new MdlStructs.VertexElement() {