// STAGE: a swarm. The chorus in numbers, flocking through a flow field.
//
// The stage owns the flocking; the song owns what is flocking. A swarm of
// notched hexagons and a swarm of hollow circles are not the same video, and
// with the old library they would have been the same scene.
//
// The members are SOLID — the chorus assembly, which is the protagonist's body
// plan with fewer parts and its own proportions (see Identity.js). A swarm is
// the case that most wants it: forty flat stamps all face the viewer, so the
// flock has one attitude, while forty bodies at forty angles have the tumbling
// look a flock actually has. Cost stays low because each member is marched only
// where its own bounding disc covers the pixel, and at this size that is a few
// percent of the frame each.

export const swarm = {
    name: 'Swarm',
    family: 'organic',
    kind: 'fragment',
    consumes: ['form', 'ink', 'staging'],
    texture: 0.5,
    traits: ['shape', 'camera', 'style'],

    params: {
        count:   { type: 'int',   range: [8, 36],     default: 24,  uniform: 'u_count', bias: 'density' },
        size:    { type: 'float', range: [0.02, 0.14],default: 0.05,uniform: 'u_size' },
        speed:   { type: 'float', range: [0.05, 0.9], default: 0.25,uniform: 'u_speed', bias: 'motion', rate: true },
        cohesion:{ type: 'float', range: [0, 1],      default: 0.5, uniform: 'u_cohesion' },
        field:   { type: 'float', range: [0.4, 3.0],  default: 1.2, uniform: 'u_field' },
        palette: { type: 'palette', count: 4 },
    },

    reactive: {
        size:  { feature: 'bandLow', amount: 0.2 },
    },

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

    vec3 col = pal(0) * 0.13;

    // Whether a member already owns this pixel — see Floating Geometry for why
    // first-wins rather than last-wins. Members are drawn small, but the song's
    // element scale can inflate them until they overlap several deep, and that
    // is the case that decides the frame budget.
    float painted = 0.0;

    for (int i = 0; i < 36; i++) {
        if (i >= u_count) break;
        float fi = float(i);
        // Each member has a home on the song's lattice and rides the same flow
        // field away from it, which is what makes it a swarm rather than a
        // scatter — cohesion decides how far it is allowed to stray.
        vec3 node = stageNode(fi, float(u_count));
        vec2 flow = curl(node.xy * u_field + t * 0.15, t * 0.1);
        vec2 pos = node.xy + flow * mix(0.05, 0.45, u_cohesion);
        pos += vec2(sin(t * 0.7 + fi), cos(t * 0.6 + fi * 1.3)) * 0.06;

        float size = max(u_size * node.z, 1e-3);
        vec2 local = (p - pos) / size;
        // A swarm is dozens of members over a mostly empty frame, so this
        // rejection is doing most of the work: without it every pixel evaluates
        // every member's distance field, and the scene measured 59ms at 4K —
        // near enough to the 60ms budget ceiling to fail on a slower GPU.
        if (dot(local, local) > 1.6 || painted > 0.5) continue;

        // They face where they go — now as a body turning into the flow rather
        // than as an outline rotating in the plane, with a roll from the same
        // field so the flock is not all level with the horizon.
        float heading = atan(flow.y, flow.x);
        mat3 turn = castTurn(heading, flow.y * 1.2 + sin(t + fi) * 0.4);

        vec3 n;
        float hit = castChorusSolid(local, turn, n);
        if (hit > 0.0) {
            painted = 1.0;
            col = mix(col, mix(castLit(n, vec3(0.0, 0.0, 1.0)), pal(i + 1), 0.4), 0.95);
        }

        // The glow and the rim keep the swarm legible at the sizes it is drawn:
        // a member is often only a few pixels across, and at that size the lit
        // faces are one pixel and the outline is the whole read.
        float d = castChorus3(turn * vec3(local, 0.0)) * size;
        col += pal(2) * 0.3 * 0.004 / (0.02 + abs(d));
        col = mix(col, pal(i + 1), inkStroke(d) * 0.8);
    }

    return vec4(inkValue(col), 1.0);
}
`,
};

export default swarm;
