// Organic family: merging metaballs on an analytic orbit.
//
// Positions come from closed-form orbits rather than any simulation, which keeps
// the scene seek-exact — the same rule the 3D particle layer follows.

export const metaballs = {
    name: 'Metaballs',
    family: 'organic',
    kind: 'fragment',
    // Personality: see look/Personality.js.
    consumes: ['cast', 'ink', 'staging'],
    traits: ['shape', 'camera', 'style'],

    params: {
        count:     { type: 'int',   range: [2, 10],    default: 5,   uniform: 'u_count', bias: 'density' },
        radius:    { type: 'float', range: [0.1, 0.6], default: 0.3, uniform: 'u_radius', bias: 'energy' },
        threshold: { type: 'float', range: [0.4, 2.2], default: 1.0, uniform: 'u_threshold' },
        speed:     { type: 'float', range: [0.03, 0.5], default: 0.15, uniform: 'u_speed', bias: 'motion', rate: true },
        spread:    { type: 'float', range: [0.2, 1.1], default: 0.6, uniform: 'u_spread' },
        rim:       { type: 'float', range: [0, 1],     default: 0.45, uniform: 'u_rim' },
        palette:   { type: 'palette', count: 5 },
    },

    reactive: {
        radius:    { feature: 'bandLow', amount: 0.25 },
        rim:       { feature: 'beat',    amount: 0.3, response: 'spike' },
        threshold: { feature: 'flux',    amount: 0.2, response: 'inverse' },
    },

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

    float field = 0.0;
    vec3 tint = vec3(0.0);

    for (int i = 0; i < 10; i++) {
        if (i >= u_count) break;
        float fi = float(i);
        float s = u_seed + fi * 71.3;

        // Homes on the song's lattice, orbited by this scene's own motion.
        vec3 node = stageNode(fi, float(u_count));
        vec2 centre = node.xy * u_spread * vec2(1.0, 0.62)
            + vec2(sin(t * (0.7 + fract(s * 0.13)) + s),
                   cos(t * (0.5 + fract(s * 0.29)) + s * 1.7)) * 0.25;

        // Distance measured in the song's FORM rather than as a circle: for a
        // round cast this is exactly length(p - centre), and for a hexagonal
        // one the blobs merge as hexagons.
        float radius = u_radius * node.z;
        float d = castMain((p - centre) / max(radius, 1e-3)) * radius + radius;
        float contribution = (radius * radius) / max(d * d, 1e-4);
        field += contribution;
        tint += pal(i) * contribution;
    }

    tint /= max(field, 1e-4);

    float surface = smoothstep(u_threshold - 0.25, u_threshold + 0.25, field);
    float rim = smoothstep(u_threshold + 0.35, u_threshold, field)
              * smoothstep(u_threshold - 0.3, u_threshold, field);

    vec3 col = pal(0) * 0.06;
    col = mix(col, tint, surface);
    col += tint * rim * u_rim;

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

export default metaballs;
