From bdcab22a5528758d5f2d54505f3ecb5b866efb7d Mon Sep 17 00:00:00 2001 From: Passive <20432486+PassiveModding@users.noreply.github.com> Date: Fri, 1 Aug 2025 22:03:27 +1000 Subject: [PATCH] Cleanup methods to extension class --- Penumbra/Import/Models/Export/MeshExporter.cs | 43 ++---------- Penumbra/Import/Models/ModelExtensions.cs | 69 +++++++++++++++++++ 2 files changed, 73 insertions(+), 39 deletions(-) create mode 100644 Penumbra/Import/Models/ModelExtensions.cs diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index 11c84677..2e41f65a 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -340,39 +340,6 @@ public class MeshExporter return typeof(VertexPositionNormalTangent); } - - private const float UnitLengthThresholdVec3 = 0.00674f; - internal static bool _IsFinite(float value) - { - return float.IsFinite(value); - } - - internal static bool _IsFinite(Vector2 v) - { - return _IsFinite(v.X) && _IsFinite(v.Y); - } - - internal static bool _IsFinite(Vector3 v) - { - return _IsFinite(v.X) && _IsFinite(v.Y) && _IsFinite(v.Z); - } - internal static Boolean IsNormalized(Vector3 normal) - { - if (!_IsFinite(normal)) return false; - - return Math.Abs(normal.Length() - 1) <= UnitLengthThresholdVec3; - } - internal static Vector3 SanitizeNormal(Vector3 normal) - { - if (normal == Vector3.Zero) return Vector3.UnitX; - return IsNormalized(normal) ? normal : Vector3.Normalize(normal); - } - internal static Vector4 SanitizeTangent(Vector4 tangent) - { - var n = SanitizeNormal(new Vector3(tangent.X, tangent.Y, tangent.Z)); - var s = float.IsNaN(tangent.W) ? 1 : tangent.W; - return new Vector4(n, s > 0 ? 1 : -1); - } /// Build a geometry vertex from a vertex's attributes. private IVertexGeometry BuildVertexGeometry(IReadOnlyDictionary> attributes) @@ -385,21 +352,19 @@ public class MeshExporter if (_geometryType == typeof(VertexPositionNormal)) return new VertexPositionNormal( ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Position)), - SanitizeNormal(ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal))) + ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal)) ); if (_geometryType == typeof(VertexPositionNormalTangent)) { // (Bi)tangents are universally stored as ByteFloat4, which uses 0..1 to represent the full -1..1 range. // TODO: While this assumption is safe, it would be sensible to actually check. - // var bitangent = ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Tangent1)) * 2 - Vector4.One; - var vec4 = ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Tangent1)); - var bitangent = vec4 with { W = vec4.W == 1 ? 1 : -1 }; + var bitangent = ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Tangent1)) * 2 - Vector4.One; return new VertexPositionNormalTangent( ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Position)), - SanitizeNormal(ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal))), - SanitizeTangent(bitangent) + ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal)), + bitangent.SanitizeTangent() ); } diff --git a/Penumbra/Import/Models/ModelExtensions.cs b/Penumbra/Import/Models/ModelExtensions.cs new file mode 100644 index 00000000..2edb3ca4 --- /dev/null +++ b/Penumbra/Import/Models/ModelExtensions.cs @@ -0,0 +1,69 @@ +namespace Penumbra.Import.Models; + +public static class ModelExtensions +{ + // https://github.com/vpenades/SharpGLTF/blob/2073cf3cd671f8ecca9667f9a8c7f04ed865d3ac/src/Shared/_Extensions.cs#L158 + private const float UnitLengthThresholdVec3 = 0.00674f; + private const float UnitLengthThresholdVec4 = 0.00769f; + + internal static bool _IsFinite(this float value) + { + return float.IsFinite(value); + } + + internal static bool _IsFinite(this Vector2 v) + { + return v.X._IsFinite() && v.Y._IsFinite(); + } + + internal static bool _IsFinite(this Vector3 v) + { + return v.X._IsFinite() && v.Y._IsFinite() && v.Z._IsFinite(); + } + + internal static bool _IsFinite(this in Vector4 v) + { + return v.X._IsFinite() && v.Y._IsFinite() && v.Z._IsFinite() && v.W._IsFinite(); + } + + internal static Boolean IsNormalized(this Vector3 normal) + { + if (!normal._IsFinite()) return false; + + return Math.Abs(normal.Length() - 1) <= UnitLengthThresholdVec3; + } + + internal static void ValidateNormal(this Vector3 normal, string msg) + { + if (!normal._IsFinite()) throw new NotFiniteNumberException($"{msg} is invalid."); + + if (!normal.IsNormalized()) throw new ArithmeticException($"{msg} is not unit length."); + } + + internal static void ValidateTangent(this Vector4 tangent, string msg) + { + if (tangent.W != 1 && tangent.W != -1) throw new ArithmeticException(msg); + + new Vector3(tangent.X, tangent.Y, tangent.Z).ValidateNormal(msg); + } + + internal static Vector3 SanitizeNormal(this Vector3 normal) + { + if (normal == Vector3.Zero) return Vector3.UnitX; + return normal.IsNormalized() ? normal : Vector3.Normalize(normal); + } + + internal static bool IsValidTangent(this Vector4 tangent) + { + if (tangent.W != 1 && tangent.W != -1) return false; + + return new Vector3(tangent.X, tangent.Y, tangent.Z).IsNormalized(); + } + + internal static Vector4 SanitizeTangent(this Vector4 tangent) + { + var n = new Vector3(tangent.X, tangent.Y, tangent.Z).SanitizeNormal(); + var s = float.IsNaN(tangent.W) ? 1 : tangent.W; + return new Vector4(n, s > 0 ? 1 : -1); + } +}