Editor/Core/Import/PolygonTriangles.cs
namespace HumanoidRigger;
using Vector2=System.Numerics.Vector2;
using Vector3=System.Numerics.Vector3;

internal static class PolygonTriangles
{
    // Ear clipping preserves concave faces and returns original corner indices,
    // keeping independent OBJ position/normal/UV indices attached to each corner.
    public static int[] Triangulate(Vector3[] points)
    {
        if(points.Length<3||points.Length>4096)throw new FormatException("Invalid or excessively large OBJ polygon.");
        if(points.Length==3)return [0,1,2];
        // Subtract a local origin before accumulating in double precision. Tiny
        // faces far from the origin otherwise lose their normal to cancellation.
        double nx=0,ny=0,nz=0;
        for(int i=1;i<points.Length-1;i++)
        {
            var a=points[i]-points[0];var b=points[i+1]-points[0];
            nx+=(double)a.Y*b.Z-(double)a.Z*b.Y;ny+=(double)a.Z*b.X-(double)a.X*b.Z;nz+=(double)a.X*b.Y-(double)a.Y*b.X;
        }
        nx=Math.Abs(nx);ny=Math.Abs(ny);nz=Math.Abs(nz);int axis=nx>ny?(nx>nz?0:2):(ny>nz?1:2);
        var projected=points.Select(p=>axis==0?new Vector2(p.Y,p.Z):axis==1?new Vector2(p.X,p.Z):new Vector2(p.X,p.Y)).ToArray();
        double Cross(Vector2 a,Vector2 b,Vector2 c)=>(double)(b.X-a.X)*(c.Y-a.Y)-(double)(b.Y-a.Y)*(c.X-a.X);
        double area=0;for(int i=1;i<points.Length-1;i++)area+=Cross(projected[0],projected[i],projected[i+1]);
        double sign=Math.Sign(area);if(sign==0)throw new FormatException("OBJ contains a degenerate polygon.");
        var pending=Enumerable.Range(0,points.Length).ToList();var result=new List<int>();
        while(pending.Count>3)
        {
            bool clipped=false;
            for(int i=0;i<pending.Count;i++)
            {
                int a=pending[(i+pending.Count-1)%pending.Count],b=pending[i],c=pending[(i+1)%pending.Count];
                if(Cross(projected[a],projected[b],projected[c])*sign<=0)continue;
                if(pending.Any(v=>v!=a&&v!=b&&v!=c&&projected[v]!=projected[a]&&projected[v]!=projected[b]&&projected[v]!=projected[c]&&Cross(projected[a],projected[b],projected[v])*sign>=0&&Cross(projected[b],projected[c],projected[v])*sign>=0&&Cross(projected[c],projected[a],projected[v])*sign>=0))continue;
                result.AddRange([a,b,c]);pending.RemoveAt(i);clipped=true;break;
            }
            if(!clipped)throw new FormatException("OBJ polygon is self-intersecting or cannot be triangulated. Triangulate it before export.");
        }
        result.AddRange(pending);return result.ToArray();
    }
}