FootballNetMotion.cs

Static math helper for a football goal net's impact response. Computes the per-frame displacement scale of the net fabric from the impact point, ball speed and time since the goal, with a contact delay, distance falloff, overshoot and decay.

using System;

namespace Socer;

// In-place net response evaluated from impact, independent of render rate.
public static class FootballNetMotion
{
    public const float Duration=3.2f;
    public static float ContactDelay(float speed)=>Math.Clamp(1.8f/Math.Max(6,speed*.58f),.06f,.30f);
    public static float Displacement(float y,float z,float impactY,float impactZ,float age,float speed)
    {
        if(age<=0 || age>=Duration)return 0;
        const float left=PitchPerspective.PostLeft,width=PitchPerspective.PostRight-PitchPerspective.PostLeft,height=PitchPerspective.CrossbarCentre;
        float u=Math.Clamp((y-left)/width,0,1),v=Math.Clamp(z/height,0,1);
        float edge=MathF.Sin(u*MathF.PI)*MathF.Sin(v*MathF.PI);
        float dy=y-Math.Clamp(impactY,left,left+width);
        float dz=z-Math.Clamp(impactZ,.12f,height-.08f);
        float distance=MathF.Sqrt(dy*dy+dz*dz);
        // The goal event is the line crossing. The ball still has to travel
        // into the back of the net before the fabric takes its impulse.
        float local=age-ContactDelay(speed)-distance/8.5f;
        if(local<=0)return 0;
        float strength=Math.Clamp(speed/22,.55f,1.25f);
        float attack=1-MathF.Exp(-local*24);
        float decay=MathF.Exp(-local*2.0f);
        float spread=MathF.Exp(-distance*.57f);
        float tail=Math.Clamp((Duration-age)/.45f,0,1);
        tail=tail*tail*(3-2*tail);
        return .66f*strength*edge*spread*attack*decay*MathF.Sin(local*9.2f)*tail;
    }
}