KeeperPose.cs

Draws the goalkeeper's dive pose for the shared HumanFigure renderer. Computes dive/tilt blending, projects body joints into screen space, and emits limb Part segments for legs, torso, arms and head.

Rough Code
using System;
using System.Collections.Generic;
namespace Socer;
public static partial class HumanFigure
{
    public static float DiveRecoverySeconds(Footballer p,MatchSimulation m)=>
        p.ActiveLegendaryAbility==LegendaryAbility.NotToday && m.Seconds<p.LegendaryActiveUntil?.34f:.65f;
    public static float DiveBlend(Footballer p,MatchSimulation m)=>p.DiveRemaining>0?Smooth((p.DiveAge-.06f)/.28f):
        1-Smooth((m.Seconds-p.DiveEndedAt)/DiveRecoverySeconds(p,m));
    static List<Part> KeeperDive(Footballer p,MatchSimulation m,List<Part> buffer=null)
    {
        var parts=buffer??new List<Part>(18);
        string skin=PlayerHeadProfiles.Skin(p.Profile.CatalogId,p.Profile.SkinTone,p.Id);
        float blend=DiveBlend(p,m);
        var look=new Point(MathF.Cos(p.Facing),MathF.Sin(p.Facing));
        var reach=(p.DiveTarget-p.DiveOrigin).Unit;if(reach.Length<.01f)reach=look;
        float reachAngle=MathF.Atan2(reach.Y,reach.X);
        float turn=MathF.Atan2(MathF.Sin(reachAngle-p.Facing),MathF.Cos(reachAngle-p.Facing));
        float heading=p.Facing+turn*blend;
        var axis=new Point(MathF.Cos(heading),MathF.Sin(heading));
        var side=new Point(-axis.Y,axis.X);
        float tilt=blend*1.35f, sin=MathF.Sin(tilt),cos=MathF.Cos(tilt);
        float flight=p.DiveRemaining>0?MathF.Sin(Math.Clamp((p.DiveAge-.08f)/.72f,0,1)*MathF.PI):0;
        float lift=flight*(.12f+Math.Min(2.2f,p.DiveHeight)*.35f);
        Point Project(Point ground,float z)=>new(ground.X,ground.Y*GroundScale-z*.77f);
        // Rigid rotation in metres: head, torso, hips and legs share the same dive axis.
        Point Joint(float lateral,float ahead,float z)
        {
            var ground=side*lateral+axis*(ahead*cos+(z-.55f)*sin);
            return Project(ground,.04f+z*cos-ahead*sin+lift);
        }
        void Bone(Point a,Point b,float width,string color)=>parts.Add(new(a,b,width,color));
        foreach(int s in new[]{-1,1})
        {
            var hip=Joint(s*.14f,0,.85f);var knee=Joint(s*.16f,-.08f-blend*.14f,.46f);
            var foot=Joint(s*.18f,-.13f-blend*(s==1?.18f:.03f),.08f);
            Bone(hip,knee,.21f,"#72602e");Bone(knee,foot,.14f,"#f5dc68");Bone(foot,foot+Project(axis*.17f,0),.16f,"#17282b");
        }
        Bone(Joint(0,0,.85f),Joint(0,0,1.39f),.48f,"#f5dc68");
        foreach(int s in new[]{-1,1})
        {
            var shoulder=Joint(s*.26f,0,1.34f);
            var physical=Project(m.GlovePosition(p,s)-p.Position,p.DiveRemaining>0?p.DiveHeight:m.Owner==p.Id?m.BallHeight:.65f);
            var hand=physical;
            // Landing tucks the hands in, then extends them back to the standing position.
            float tuck=p.DiveRemaining>0?Smooth((p.DiveAge-.65f)/.3f):blend;
            var resting=Joint(s*.22f,.15f,1.05f);
            hand=hand*(1-tuck)+resting*tuck;
            var delta=hand-shoulder;
            if(delta.Length>.72f)hand=shoulder+delta.Unit*.72f;
            delta=hand-shoulder;
            var normal=new Point(-delta.Y,delta.X).Unit;
            var elbow=(shoulder+hand)*.5f+normal*(s*.1f);
            Bone(shoulder,elbow,.17f,"#f5dc68");Bone(elbow,hand,.12f,skin);Bone(hand,hand+new Point(.02f,0),.25f,"#fff8dd");
        }
        Bone(Joint(0,0,1.48f),Joint(0,0,1.72f),.31f,skin);
        Bone(Joint(0,-.015f,1.72f),Joint(0,-.015f,1.78f),.29f,"#303137");
        return parts;
    }
}