The static class validates mesh surface orientation in the editor. It calculates alignment between the posed normal and the bind normal transported through skinning bone rotations and weights.
namespace HumanoidRigger.EditorTools.Core.Validation;
using Vector3=System.Numerics.Vector3;
using Quaternion=System.Numerics.Quaternion;
/// <summary>Compare the deformed face with its skin-transported bind normal.
/// Whole-body rotation preserves agreement; a reversed local volume does not.</summary>
public static class SurfaceOrientation
{
public const float ReversalLimit=-.2f;
public static float Alignment(Vector3 bindNormal,Vector3 posedNormal,Influence[] a,Influence[] b,Influence[] c,Quaternion[] rotations)
=>Measure(bindNormal,posedNormal,a,b,c,rotations).Alignment;
public static (float Alignment,float VolumeRatio) Measure(Vector3 bindNormal,Vector3 posedNormal,Influence[] a,Influence[] b,Influence[] c,Quaternion[] rotations)
{
var transported=Transported(bindNormal,a,b,c,rotations);
float denominator=posedNormal.Length()*transported.Length();
float dot=Vector3.Dot(posedNormal,transported);
float alignment=denominator>0&&float.IsFinite(denominator)?Math.Clamp(dot/denominator,-1,1):float.NaN;
// This is the signed volume ratio of an infinitesimal prism whose vertex
// weights extend along the bind normal. It includes collapse, not just tilt.
return(alignment,dot/bindNormal.LengthSquared());
}
/// <summary>The bind normal carried by the face's own skin: where the
/// deformed face should point, with the bind area still in its length.</summary>
public static Vector3 Transported(Vector3 bindNormal,Influence[] a,Influence[] b,Influence[] c,Quaternion[] rotations)
{
// A triangle's corners nearly always share their bones. Carry the normal
// once per bone; each influence then adds the same value in the same order.
const int Capacity=16;
Span<int> bones=stackalloc int[Capacity];Span<Vector3> carried=stackalloc Vector3[Capacity];int known=0;
var transported=Vector3.Zero;
for(int corner=0;corner<3;corner++)foreach(var w in corner==0?a:corner==1?b:c)
{
int at=0;while(at<known&&bones[at]!=w.Bone)at++;
Vector3 normal;
if(at<known)normal=carried[at];
else
{
normal=Vector3.Transform(bindNormal,rotations[w.Bone]);
if(known<Capacity){bones[known]=w.Bone;carried[known++]=normal;}
}
transported+=normal*(w.Weight/3);
}
return transported;
}
}