Editor/Core/Rigging/SkinningNormals.cs
namespace HumanoidRigger;
using Vector3=System.Numerics.Vector3;

/// <summary>Angle-weighted outward normals for consistently oriented components.
/// A shell with openings, such as a jacket or a sleeve, still has an inside
/// when its faces enclose a clear volume; only a sheet or an inconsistently
/// wound surface contributes no directional prior.</summary>
internal static class SkinningNormals
{
    internal static Vector3[] ClosedSurface(Vector3[] points,int[] triangles)
    {
        var mesh=new MeshPart("Skinning surface",points,triangles,MeshKind.Body);
        var components=Geometry.Components(mesh);int count=components.Max()+1;
        var origins=new Vector3[count];var assigned=new bool[count];
        for(int v=0;v<points.Length;v++)if(!assigned[components[v]]){origins[components[v]]=points[v];assigned[components[v]]=true;}
        var volumes=new double[count];var areas=new double[count];var closed=Enumerable.Repeat(true,count).ToArray();
        var normals=new Vector3[points.Length];var edges=new Dictionary<(int,int),(int Count,int Direction)>();
        for(int t=0;t<triangles.Length;t+=3)
        {
            int a=triangles[t],b=triangles[t+1],c=triangles[t+2];if(a==b||a==c||b==c)continue;
            var normal=Vector3.Cross(points[b]-points[a],points[c]-points[a]);float area=normal.Length();
            if(area<1e-12f){closed[components[a]]=false;continue;}
            normal/=area;areas[components[a]]+=area*.5;
            var origin=origins[components[a]];
            volumes[components[a]]+=Vector3.Dot(points[a]-origin,Vector3.Cross(points[b]-origin,points[c]-origin));
            for(int corner=0;corner<3;corner++)
            {
                int v=triangles[t+corner],n=triangles[t+(corner+1)%3],o=triangles[t+(corner+2)%3];
                var x=points[n]-points[v];var y=points[o]-points[v];
                normals[v]+=normal*MathF.Atan2(Vector3.Cross(x,y).Length(),Vector3.Dot(x,y));
                var key=(Math.Min(v,n),Math.Max(v,n));var edge=edges.GetValueOrDefault(key);
                edges[key]=(edge.Count+1,edge.Direction+(v<n?1:-1));
            }
        }
        // A shared edge wound the same way twice is inconsistent; an edge used
        // once is an opening. The enclosed volume of a shell with openings is
        // taken from a point on the shell itself, so a sheet sums to nothing
        // while a sleeve or a jacket keeps a clear sign.
        foreach(var edge in edges)if(edge.Value.Count>2||edge.Value.Count==2&&edge.Value.Direction!=0)closed[components[edge.Key.Item1]]=false;
        for(int v=0;v<normals.Length;v++)
        {
            int component=components[v];float length=normals[v].Length();
            normals[v]=closed[component]&&Math.Abs(volumes[component])>.02*Math.Pow(areas[component],1.5)&&length>1e-6f
                ?normals[v]*(Math.Sign(volumes[component])/length):Vector3.Zero;
        }
        return normals;
    }
}