// Ported from party-stage's "Floating Geometry". Shape count, size and motion
// were fixed constants in the original; they are the whole point of the scene,
// so they are now params the look generator can move.
//
// The bodies are SOLID. This scene is the one in the library whose whole
// premise is objects adrift with nothing under them, and adrift is a thing that
// only reads in three dimensions: a flat silhouette rotating is a sticker
// turning, and no amount of drift makes it a body. Each element is now an
// instance of the song's assembly, marched in its own frame — so a shape hides
// its own far side, and two of them at different angles are visibly the same
// object seen twice rather than two copies of one outline. See castSolid.

export const floatingGeometry = {
    name: 'Floating Geometry',
    family: 'geometric',
    kind: 'fragment',
    // Personality: see look/Personality.js. This scene is nothing but a handful
    // of shapes, so `shape` is the trait it exists to express.
    // Takes the track's surface grain, but lightly — this is drawn, not filmed.
    texture: 0.4,
    consumes: ['form', 'ink', 'staging'],
    traits: ['shape', 'camera', 'style'],

    params: {
        count:     { type: 'int',   range: [2, 14],    default: 6,   uniform: 'u_count', bias: 'density' },
        size:      { type: 'float', range: [0.04, 0.3],default: 0.15,uniform: 'u_size' },
        drift:     { type: 'float', range: [0.1, 1.5], default: 0.4, uniform: 'u_drift', bias: 'motion', rate: true },
        spin:      { type: 'float', range: [0, 2],     default: 0.6, uniform: 'u_spin', rate: true },
        variety:   { type: 'float', range: [0, 0.6],   default: 0.25, uniform: 'u_variety' },
        spread:    { type: 'float', range: [0.3, 1.2], default: 0.8, uniform: 'u_spread' },
        palette:   { type: 'palette', count: 5 },
    },

    reactive: {
        size: { feature: 'beat',    amount: 0.18, response: 'spike' },
    },

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

    // Background wash from the two darkest palette entries.
    vec3 col = mix(pal(0) * 0.18, pal(1) * 0.24, sin(t) * 0.5 + 0.5);

    // Whether a body already owns this pixel. The elements all float at the same
    // depth, so which one wins where they overlap was always arbitrary — it used
    // to be whichever came last. Making it whichever comes FIRST costs nothing
    // visually and bounds the work at roughly one march per pixel: at the top of
    // the size range fourteen bodies cover the frame several deep, and marching
    // every one of them at every pixel was slow enough that the scene gate ran
    // for minutes without finishing.
    float painted = 0.0;

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

        // The song's lattice sets where they hang; this scene keeps the float.
        vec3 node = stageNode(fi, float(u_count));
        vec2 pos = node.xy * u_spread * vec2(1.0, 0.55)
            + vec2(sin(t * 0.5 + s), cos(t * 0.3 + s * 1.1)) * 0.18;

        float size = u_size * node.z * (0.6 + fract(s * 0.7) * 0.8);

        // Every element is the song's protagonist. This scene used to pick
        // between a box and a circle per element, which is precisely the choice
        // the production design should be making — one video, one cast.
        // The variety param only scales them apart; it never changes what they are.
        float scale = size * (1.0 + (fract(s * 0.37) - 0.5) * u_variety);

        // Each body turns on its own two axes, at its own rate. The spread of
        // angles is what makes the field read as one object seen from several
        // sides rather than as a row of identical stamps — the thing a flat
        // silhouette could not do however fast it span.
        mat3 turn = castTurn(t * (0.2 + fract(s) * u_spin) + s,
                             sin(t * 0.4 + s * 2.1) * 0.9);

        vec2 local = (p - pos) / max(scale, 1e-3);
        // Nothing this element can contribute to this pixel. Cheap to ask, and
        // it is what keeps the cost proportional to the elements a pixel
        // actually touches rather than to the elements in the frame — without
        // it the slice distance below is evaluated fourteen times per pixel.
        if (dot(local, local) > 1.6 || painted > 0.5) continue;

        vec3 n;
        float hit = castSolid(local, turn, n);

        vec3 shapeColor = pal(i + int(floor(t * 0.3)));
        if (hit > 0.0) {
            painted = 1.0;
            // Lit in the track's key light, then pulled toward this element's
            // own palette entry so the field keeps the colour rhythm it had.
            col = mix(castLit(n, vec3(0.0, 0.0, 1.0)), shapeColor, 0.35);
        }

        // The halo and the rim survive the migration: the halo is what stops a
        // dark body on a dark wash from disappearing, and the rim is the body's
        // outline drawn in the track's line weight. Both read off the object's
        // own slice at this angle rather than off a separate flat profile, so
        // there is still exactly one shape on screen.
        float d = castSDF3(turn * vec3(local, 0.0)) * scale;
        col += shapeColor * (1.0 - smoothstep(0.0, size * 2.2, abs(d))) * 0.25;
        col = mix(col, shapeColor, inkStroke(d) * 0.7);
    }

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

export default floatingGeometry;
