// Flow family: a thermal plume rising from a source on the track's horizon.
//
// Distinct from Aurora Veil (sheets that stand still and ripple sideways), Curl
// Flow (an isotropic field with no origin) and Firefly Drift (discrete motes):
// everything here moves in one direction, and it WIDENS as it goes. The plume
// has a source, a body and a dissipating head, which is a shape none of those
// three can make — an isotropic field has no up.
//
// Scaffolded by tools/new-scene.js. See HOWTO-visualizers.md.

export const smokeColumn = {
    name: 'Smoke Column',
    family: 'flow',
    kind: 'fragment',
    traits: ['camera', 'space', 'style'],

    params: {
        scale:    { type: 'float', range: [1, 9],       default: 3.4,  uniform: 'u_scale', bias: 'density' },
        speed:    { type: 'float', range: [0.05, 1.2],  default: 0.32, uniform: 'u_speed', bias: 'motion', rate: true },
        detail:   { type: 'float', range: [0.2, 2.5],   default: 1.1,  uniform: 'u_detail', bias: 'density' },
        glow:     { type: 'float', range: [0, 1.5],     default: 0.5,  uniform: 'u_glow', bias: 'energy' },
        spread:   { type: 'float', range: [0.15, 1.4],  default: 0.55, uniform: 'u_spread' },
        buoyancy: { type: 'float', range: [0.3, 2.5],   default: 1.1,  uniform: 'u_buoyancy' },
        ember:    { type: 'float', range: [0, 1],       default: 0.35, uniform: 'u_ember', bias: 'energy' },
        palette:  { type: 'palette', count: 5 },
    },

    reactive: {
        glow:   { feature: 'bandLow',  amount: 0.3,  response: 'smooth' },
        detail: { feature: 'bandHigh', amount: 0.25, response: 'smooth' },
        ember:  { feature: 'beat',     amount: 0.25, response: 'spike' },
    },

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

    float source = sigHorizonY() * 0.6 - 0.85;
    float rise = p.y - source;                     // 0 at the vent, up the frame

    // The cone: the plume widens with height, so the same turbulence covers
    // more of the frame the further it has travelled. Dividing x by the cone
    // width before sampling is what makes the noise splay rather than shear.
    float cone = u_spread * (0.25 + max(rise, 0.0) * 0.9);
    float across = p.x / max(cone, 0.05);

    // Advection: the noise field is sampled at a y that moves DOWN with time,
    // which reads as the smoke moving up. Buoyancy accelerates it with height,
    // because hot gas does.
    float climb = t * (0.6 + max(rise, 0.0) * u_buoyancy * 0.5);
    vec2 q = vec2(across * 0.9, rise * 1.6 - climb);

    float body = fbm(q * u_scale * 0.35 + vec2(0.0, 0.0), 5);
    float curls = fbm(q * u_scale * 0.9 + body * u_detail, 4);
    float density = body * 0.65 + curls * 0.35;

    // Envelope: nothing outside the cone, nothing below the vent, and the head
    // dissipates. Without the last term the column just leaves the top of frame.
    float inside = exp(-across * across * 0.9);
    float above = smoothstep(-0.05, 0.25, rise);
    float head = 1.0 - smoothstep(0.7, 2.1, rise);
    float plume = density * inside * above * head;

    vec3 col = pal(0) * 0.05;
    col += palRamp(0.15 + density * 0.35 + rise * 0.08) * plume * 1.15;
    col += pal(4) * pow(sat(plume), 3.0) * u_glow * 0.6;

    // Embers: bright points carried up inside the column, on their own hashed
    // lanes. Local, so the frame never swings as a whole.
    if (u_ember > 0.01) {
        float lane = floor(across * 4.0);
        float lanePhase = fract(hash11(lane + u_seed) + t * 0.4);
        float emberY = mix(0.0, 1.8, lanePhase);
        float d = length(vec2(p.x - lane * cone * 0.25, rise - emberY));
        col += pal(3) * exp(-d * 40.0) * u_ember * (1.0 - lanePhase) * inside;
    }

    // The vent itself.
    col += pal(2) * exp(-length(vec2(p.x * 2.2, rise * 5.0))) * (0.35 + u_glow * 0.5);

    col = sigAir(col, p, smoothstep(0.0, 1.8, length(p)));
    col += sigGrain(uv);
    return vec4(col, 1.0);
}
`,
};

export default smokeColumn;
