// Flow family: a vortex street. One bluff body sits still in a moving stream and
// sheds vortices alternately off each shoulder; they drift downstream, growing
// and weakening, curling the streamlines as they pass.
//
// Every other flow scene moves the whole frame — Vortex Drift spins it, Curl
// Flow advects it, Aurora Veil sweeps it. This one has a fixed obstacle and a
// wake, so the image has an upstream and a downstream and a still point in it,
// and the motion is a consequence of the body rather than the state of the
// frame. The body is the track's signature form.

export const karmanStreet = {
    name: 'Kármán Street',
    family: 'flow',
    kind: 'fragment',
    texture: 0.7,
    traits: ['shape', 'camera', 'space', 'style'],

    params: {
        shed:    { type: 'float', range: [0.03, 0.5],  default: 0.12, uniform: 'u_shed', bias: 'motion', rate: true },
        drift:   { type: 'float', range: [0.15, 0.7],  default: 0.42, uniform: 'u_drift' },
        offset:  { type: 'float', range: [0.05, 0.5],  default: 0.22, uniform: 'u_offset' },
        radius:  { type: 'float', range: [0.06, 0.3],  default: 0.15, uniform: 'u_radius' },
        body:    { type: 'float', range: [0.05, 0.28], default: 0.13, uniform: 'u_body' },
        streams: { type: 'float', range: [4, 44],      default: 16,   uniform: 'u_streams', bias: 'density' },
        warp:    { type: 'float', range: [0, 0.5],     default: 0.22, uniform: 'u_warp' },
        wake:    { type: 'float', range: [0, 1],       default: 0.35, uniform: 'u_wake', slowAxis: true },
        glow:    { type: 'float', range: [0, 1.4],     default: 0.55, uniform: 'u_glow', bias: 'energy' },
        palette: { type: 'palette', count: 5 },
    },

    reactive: {
        glow:   { feature: 'loudness', amount: 0.3,  response: 'smooth' },
        radius: { feature: 'bandLow',  amount: 0.25, response: 'smooth' },
    },

    shader: `
vec4 scene(vec2 uv, vec2 p) {
    float t = u_time * u_shed + u_seed;
    p = sigCamera(p);

    // The stream runs along the track's horizon, so the wake sits in the same
    // place the ground does in every other scene that has one.
    float lane = sigHorizonY() * 0.35;
    vec2 origin = vec2(-0.85, lane);

    // Eight vortices in flight at once: one shed per unit of t, alternating
    // sides, each one older and larger than the one behind it.
    vec2 vel = vec2(0.0);
    float vort = 0.0;
    // lint: fixed-cost — eight vortices in flight, not a search
    for (int i = 0; i < 8; i++) {
        float age = fract(t) + float(i);
        float idx = floor(t) - float(i);
        float sgn = mod(idx, 2.0) < 0.5 ? 1.0 : -1.0;

        vec2 c = origin + vec2(age * u_drift,
                               sgn * u_offset * (1.0 - exp(-age * 1.8)) + sin(age * 0.6 + idx) * 0.02);
        float rad = u_radius * (0.55 + age * 0.16);

        vec2 d = p - c;
        float r2 = dot(d, d) + 1e-4;
        float fall = exp(-r2 / (rad * rad));

        // Induced velocity, bounded at the core so the centre does not blow up.
        vel += sgn * vec2(-d.y, d.x) * (fall / (r2 + rad * rad)) * rad;
        vort += sgn * fall * exp(-age * 0.25);
    }

    // Streamlines: a plain ruled field, dragged by the induced velocity. The
    // curl in the image is entirely the vortices bending straight lines.
    vec2 q = p - vel * u_warp;
    float ruled = sin(q.y * u_streams) * 0.5 + 0.5;
    float w = 0.25 + u_sigLine * 0.4;
    float line = smoothstep(w, w * 0.2, abs(ruled - 0.5) * 2.0);

    // Turbulent haze: everything downstream of the body is stirred, and the
    // band of stirred water widens across the track. That is the scene's long
    // journey — a clean stream at the top of the video and a churned one at the
    // end — so the haze both lights the wake and dissolves the ruled lines
    // inside it, rather than being a wash laid over an unchanged image.
    float lanePos = (p.y - lane) / (0.1 + u_wake * 0.8);
    float haze = exp(-lanePos * lanePos) * smoothstep(-0.15, 0.6, p.x - origin.x);
    line *= 1.0 - 0.85 * haze * u_wake;

    vec3 col = mix(pal(0) * 0.08, pal(1) * 0.14, uv.y);
    col += palRamp(0.45 + q.y * 0.05) * line * (0.35 + u_glow * 0.45);
    col += pal(2) * haze * u_wake * 0.9;
    col += pal(4) * haze * sat(abs(vort)) * u_wake * 0.5;

    // The cores themselves, warm one way and cool the other.
    col += pal(3) * sat(vort) * u_glow * 0.5;
    col += pal(2) * sat(-vort) * u_glow * 0.5;

    // The obstacle: still, solid, with the stream piling up on its nose.
    float d = sigShape((p - origin) / u_body) * u_body;
    col = mix(col, pal(0) * 0.1, smoothstep(0.005, -0.005, d));
    col += pal(4) * sigEdge(d) * (0.5 + u_glow * 0.5);
    col += pal(4) * exp(-abs(d) * 14.0) * smoothstep(0.1, -0.3, p.x - origin.x) * u_glow * 0.35;

    col = sigAir(col, p, smoothstep(0.0, 1.6, length(p - origin) * 0.7));
    col += sigGrain(uv);
    return vec4(col, 1.0);
}
`,
};

export default karmanStreet;
