From f5f4fe7259cb8aeec36cad45e2bc342eda3f109f Mon Sep 17 00:00:00 2001
From: Passive <20432486+PassiveModding@users.noreply.github.com>
Date: Thu, 31 Jul 2025 23:07:22 +1000
Subject: [PATCH] Invalid tangent fix example
---
Penumbra/Import/Models/Export/MeshExporter.cs | 45 ++++++++++++++++---
1 file changed, 40 insertions(+), 5 deletions(-)
diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs
index 0070a808..11c84677 100644
--- a/Penumbra/Import/Models/Export/MeshExporter.cs
+++ b/Penumbra/Import/Models/Export/MeshExporter.cs
@@ -340,6 +340,39 @@ 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)
@@ -352,19 +385,21 @@ public class MeshExporter
if (_geometryType == typeof(VertexPositionNormal))
return new VertexPositionNormal(
ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Position)),
- ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal))
+ SanitizeNormal(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 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 };
+
return new VertexPositionNormalTangent(
ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Position)),
- ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal)),
- bitangent
+ SanitizeNormal(ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal))),
+ SanitizeTangent(bitangent)
);
}