// Flow family: the field lines of three drifting magnetic poles, drawn the way
// iron filings draw them — as closed loops leaving one pole and arriving at the
// next, thickening where the field is strong.
//
// Curl Flow and Smoke Column sample a noise field and let particles wander in
// it; nothing about the image is solved. Here the lines are the exact contours
// of the stream function of the poles, so they never cross, never fray, and
// reconfigure globally the instant a pole moves — a field with rules rather than
// a field with texture. The poles themselves are the track's signature form.

export const magnetLines = {
    name: 'Magnet Lines',
    family: 'flow',
    kind: 'fragment',
    // Line work. Grain furs it up, so take only a dusting.
    texture: 0.5,
    consumes: ['cast', 'ink'],
    traits: ['shape', 'camera', 'style'],

    params: {
        lines:   { type: 'int',   range: [3, 26],       default: 11,   uniform: 'u_lines', bias: 'density' },
        spread:  { type: 'float', range: [0.15, 1.1],   default: 0.55, uniform: 'u_spread', slowAxis: true },
        orbit:   { type: 'float', range: [0.01, 0.3],   default: 0.05, uniform: 'u_orbit', bias: 'motion', rate: true },
        core:    { type: 'float', range: [0.02, 0.22],  default: 0.09, uniform: 'u_core' },
        filings: { type: 'float', range: [0, 1],        default: 0.45, uniform: 'u_filings' },
        palette: { type: 'palette', count: 5 },
    },

    reactive: {
        core:   { feature: 'beat',    amount: 0.2,  response: 'spike' },
    },

    shader: `
// Pole i, on a slow lissajous so the three never settle into a rotation.
vec2 poleAt(int i, float t) {
    float fi = float(i);
    float a = t + fi * 2.0944;
    return vec2(cos(a), sin(a * 1.31 + fi)) * u_spread;
}

vec4 scene(vec2 uv, vec2 p) {
    float t = u_time * u_orbit + u_seed;
    p = sigCamera(p);

    // Stream function of the poles. Its contours ARE the field lines, which is
    // why they close on themselves for free. Charges sum to +1 so the whole
    // thing wraps once per turn at infinity, and an integer contour count keeps
    // that wrap continuous instead of leaving a seam.
    float psi = 0.0;
    float energy = 0.0;
    // lint: fixed-cost — three poles, not a search
    for (int i = 0; i < 3; i++) {
        vec2 d = p - poleAt(i, t);
        float q = mod(float(i), 2.0) < 0.5 ? 1.0 : -1.0;
        psi += q * atan(d.y, d.x);
        energy += 1.0 / (dot(d, d) * 14.0 + 0.35);
    }

    float f = psi * float(u_lines) / 6.28318530718;
    float band = abs(fract(f) - 0.5) * 2.0;      // 0 on the line, 1 between lines

    // Lines are thin where the field is weak and thick where it crowds, which
    // is the whole visual signature of filings.
    float w = (0.18 + u_sigLine * 0.5) * (0.35 + sat(energy) * 0.9);
    float line = smoothstep(w, w * 0.15, band);

    // Filings: the line is not solid, it is a queue of grains along it.
    float grainAlong = vnoise(vec2(f * 40.0, psi * 3.0 + t));
    line *= mix(1.0, smoothstep(0.25, 0.75, grainAlong), u_filings);

    vec3 col = pal(0) * 0.06;
    col += palRamp(0.15 + fract(f) * 0.15 + energy * 0.1) * line * (0.6 + 0.6 * 0.7);
    col += pal(2) * sat(energy) * 0.12 * 0.6;

    // The poles: the track's form, lit from inside, one hot and one cold.
    // lint: fixed-cost — the same three poles
    for (int i = 0; i < 3; i++) {
        vec2 c = poleAt(i, t);
        float d = castMain((p - c) / u_core) * u_core;
        vec3 tint = mod(float(i), 2.0) < 0.5 ? pal(3) : pal(4);
        col = mix(col, tint * 0.25, smoothstep(0.004, -0.004, d));
        col += tint * inkStroke(d) * (0.5 + 0.6 * 0.6);
    }

    col *= 0.7 + 0.3 * exp(-dot(p, p) * 0.3);
    return vec4(inkValue(col), 1.0);
}
`,
};

export default magnetLines;
