// Geometric family: two rotating line grids interfering.
//
// Moiré is a spatial-aliasing effect by nature, so this scene is the most likely
// in the library to alias badly. Line width is held above a floor and scaled by
// u_pixelScale, which is what keeps a 720p preview and a 4K export looking the
// same rather than the preview shimmering.

export const moireGrid = {
    name: 'Moiré Grid',
    family: 'geometric',
    kind: 'fragment',
    // Personality: see look/Personality.js.
    // Crisp line work: the track's surface grain would only fur the edges.
    texture: 0,
    consumes: ['ink'],
    traits: ['camera', 'style'],

    params: {
        // The subject: the song's protagonist standing in the field, with the
        // interference running at a different phase inside it. A window rather
        // than a shape laid on top — the pattern is still the picture, it just
        // has somewhere to be about, and the edge gives the eye a line to track.
        subject: { type: 'float', range: [0, 1],       default: 0.7,  uniform: 'u_subject' },
        subjSize: { type: 'float', range: [0.2, 0.75], default: 0.42, uniform: 'u_subjSize' },
        density:  { type: 'float', range: [6, 60],   default: 22,  uniform: 'u_density', bias: 'density' },
        offset:   { type: 'float', range: [0.0, 0.5], default: 0.08, uniform: 'u_offset' },
        rotate:   { type: 'float', range: [0, 0.25], default: 0.04, uniform: 'u_rotate', bias: 'motion', rate: true },
        // Named u_lineWidth, not u_width: the shader contract already declares
        // `uniform float u_width` for stereo width, and a colliding name is a
        // redefinition error that renders the scene as a black frame.
        width:    { type: 'float', range: [0.06, 0.5], default: 0.2, uniform: 'u_lineWidth' , slowAxis: true },
        warp:     { type: 'float', range: [0, 1],    default: 0.25, uniform: 'u_warp' },
        palette:  { type: 'palette', count: 4 },
    },

    reactive: {
        offset: { feature: 'bandLow', amount: 0.35 },
        warp:   { feature: 'flux',    amount: 0.2,  response: 'smooth' },
    },

    shader: `
// Anti-aliased line grid: the smoothstep edge is widened by the screen-space
// derivative, so lines stay a consistent visual weight at any resolution.
float grid(vec2 q, float density, float width) {
    vec2 g = q * density;
    vec2 f = abs(fract(g) - 0.5);
    float d = min(f.x, f.y);
    float aa = max(fwidth(d), 0.001);
    return smoothstep(width * 0.5 + aa, width * 0.5 - aa, d);
}

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

    vec2 warp = vec2(fbm(p * 1.5 + t, 3), fbm(p * 1.5 - t, 3)) - 0.5;
    vec2 q = p + warp * u_warp;

    // Drawn in the track's hand: its line weight scales this scene's.
    float weight = u_lineWidth * (0.5 + u_sigLine);
    float a = grid(rot(t * 6.28318530718) * q, u_density, weight);
    float b = grid(rot(-t * 6.28318530718 + u_offset * 3.14159) * (q + u_offset), u_density, weight);

    // The subject does something TO the field, and which thing is the song's
    // decision rather than this scene's — the same form punching a hole,
    // bending the grid or running it at another rate are three different
    // videos. See Identity.IMPACTS.
    float d = subjectSDF(p, u_subjSize);
    float inside = smoothstep(0.01, -0.01, d) * u_subject;

    // WARP bends the pattern around the form instead of replacing it inside,
    // so it is applied to the coordinate everything below is drawn from.
    if (impactIs(1.0)) q = subjectWarp(q, u_subjSize, u_subject * 1.2);

    vec2 qi = q + vec2(u_offset * 0.7, -u_offset * 0.4);
    float ai = grid(rot(t * 6.28318530718 * 1.35) * qi, u_density * 1.18, weight);
    float bi = grid(rot(-t * 6.28318530718 * 0.7 + u_offset * 3.14159) * (qi + u_offset), u_density * 0.86, weight);

    // The interference term is the point: where both grids land, it peaks.
    float interference = a * b;
    float either = max(a, b);

    if (impactIs(0.0)) {                    // shift: a different beat inside
        interference = mix(interference, ai * bi, inside);
        either = mix(either, max(ai, bi), inside);
    } else if (impactIs(2.0)) {             // punch: the field stops at the form
        interference *= 1.0 - inside;
        either *= 1.0 - inside;
    } else if (impactIs(3.0)) {             // morph: one grid survives inside
        interference = mix(interference, a * 0.35, inside);
        either = mix(either, a, inside);
    } else if (impactIs(4.0)) {             // overlay: the form reads as solid
        either = max(either, inside);
        interference = max(interference, inside * 0.6);
    }

    vec3 col = pal(0) * 0.05;
    col += pal(1) * either * 0.35;
    col += pal(2) * interference * (0.8 + 0.35);

    // The rim, so the form has an edge to follow rather than only a change of
    // texture. Drawn in the song's hand like everything else.
    col = mix(col, pal(3), inkStroke(d) * u_subject);

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

export default moireGrid;
