Editor/Core/Analysis/SparseFingerFitting.cs
namespace HumanoidRigger;
using Vector3=System.Numerics.Vector3;

/// <summary>Extend short cap-derived chains using a resolved neighboring knuckle
/// as a prior. All new segments must remain inside their own finger volume.</summary>
internal static class SparseFingerFitting
{
    internal static void Refine(ImportedCharacter character,Anatomy anatomy,string side)
    {
        var chains=Profiles.Fingers.Skip(1).Select(f=>new[]{f+"1."+side,f+"2."+side,f+"3."+side,f+"Tip."+side})
            .Where(r=>r.All(k=>anatomy.Points.TryGetValue(k,out var p)&&!p.Corrected&&p.Confidence>=.35f)).ToArray();
        if(chains.Length<3||!anatomy.Hands.TryGetValue(side,out var frame))return;
        var axis=frame.Forward;float h=anatomy.Height;
        float Length(string[] r)=>Vector3.Distance(anatomy[r[0]],anatomy[r[3]]);
        var reference=chains.MaxBy(Length)!;float longest=Length(reference);
        float knuckle=Vector3.Dot(anatomy[reference[0]],axis);
        var volume=new SurfaceVisibility(character.Meshes.Where(m=>m.Kind==MeshKind.Body),h*1e-5f);
        var surface=character.Meshes.Where(m=>m.Kind==MeshKind.Body).SelectMany(m=>m.Vertices).ToArray();
        foreach(var roles in chains)
        {
            if(Length(roles)>longest*.65f)continue;
            var tip=anatomy[roles[3]];float extension=Vector3.Dot(tip,axis)-knuckle;
            if(extension<=Length(roles)*1.25f||extension>longest*1.25f)continue;
            var root=tip-axis*extension;
            // Partition neighboring shafts by their detected tips. Nearby ring
            // and pinky vertices must not pull a section across the finger gap.
            var local=surface.Where(p=>Vector3.Distance(p,Geometry.ClosestOnSegment(p,root,tip))<h*.015f)
                .Where(p=>chains.All(other=>other==roles||Vector3.Cross(p-tip,axis).LengthSquared()<=Vector3.Cross(p-anatomy[other[3]],axis).LengthSquared())).ToArray();
            var fitted=new[]{0f,.5f,.78f,1f}.Select(t=>
            {
                var seed=Vector3.Lerp(root,tip,t);
                return t==1||local.Length<4?seed:BodyDetector.RefineCenter(local,seed,axis,h*.006f);
            }).ToArray();
            if(Enumerable.Range(0,3).Any(b=>Enumerable.Range(0,10).Any(s=>!volume.Contains(Vector3.Lerp(fitted[b],fitted[b+1],s/10f),h*1e-5f))))continue;
            for(int joint=0;joint<3;joint++)anatomy.Points[roles[joint]]=anatomy.Points[roles[joint]] with{Position=fitted[joint]};
        }
    }
}