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.
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;
}
}