// Geometric family: the interference of several plane waves at irrational
// angles — a pattern with local symmetry that never actually repeats.
//
// Distinct from Moiré Grid (two periodic grids beating against each other),
// Truchet Fold (a tiling, so a cell structure you can see) and Prism Bloom
// (radial geometry around a centre): with five or seven waves the sum is
// quasiperiodic. There is no tile, no cell and no centre, and the eye keeps
// finding order that does not survive being looked at twice. That is a texture
// the library could not previously make.
//
// Scaffolded by tools/new-scene.js. See HOWTO-visualizers.md.

export const quasicrystal = {
    name: 'Quasicrystal',
    family: 'geometric',
    kind: 'fragment',
    // Crisp line work: the track's surface grain would only fur the edges.
    texture: 0,
    consumes: ['ink'],
    traits: ['camera', 'style'],

    params: {
        waves:     { type: 'int',   range: [3, 11],     default: 5,    uniform: 'u_waves', bias: 'density' },
        frequency: { type: 'float', range: [2, 26],     default: 9,    uniform: 'u_frequency', bias: 'density' },
        speed:     { type: 'float', range: [0.02, 0.7], default: 0.15, uniform: 'u_speed', bias: 'motion', rate: true },
        contrast:  { type: 'float', range: [0.5, 6],    default: 2.0,  uniform: 'u_contrast' },
        threshold: { type: 'float', range: [0, 0.9],    default: 0.35, uniform: 'u_threshold' },
        breathe:   { type: 'float', range: [0, 0.5],    default: 0.15, uniform: 'u_breathe' },
        palette:   { type: 'palette', count: 5 },
    },

    reactive: {
        threshold: { feature: 'bandLow',  amount: 0.2,  response: 'smooth' },
    },

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

    // Sum of N plane waves, each rotated by pi/N. When N is odd the angles are
    // incommensurate with any lattice, which is exactly why the result never
    // tiles — and why an even N looks disappointingly like a grid.
    float sum = 0.0;
    float n = max(float(u_waves), 1.0);
    float freq = u_frequency * (1.0 + sin(t * 0.5) * u_breathe);

    for (int i = 0; i < 11; i++) {
        if (i >= u_waves) break;
        float a = 3.14159265 * float(i) / n + t * 0.08;
        vec2 dir = vec2(cos(a), sin(a));
        sum += cos(dot(p, dir) * freq + t * 1.7 + float(i) * 0.6);
    }

    float field = sum / n;

    // Contrast shapes the sum into either soft blobs or hard cells; the
    // threshold cuts it into the flat-topped plateaus that read as a lattice of
    // rosettes rather than as a ripple.
    float shaped = tanh(field * u_contrast);
    float plateau = smoothstep(u_threshold - 0.12, u_threshold + 0.12, shaped);

    vec3 col = mix(pal(0) * 0.08, palRamp(0.15 + field * 0.35), 0.5 + shaped * 0.5);
    col = mix(col, pal(2), plateau * 0.55);

    // Ridges: where the sum crosses zero, which is the aperiodic skeleton.
    float ridge = 1.0 - abs(shaped);
    col += pal(3) * inkStroke(shaped) * 0.5 * 0.3;

    col *= 0.65 + 0.35 * exp(-dot(p, p) * 0.22);
    return vec4(inkValue(col), 1.0);
}
`,
};

export default quasicrystal;
