// Structural family: an overhead truss, seen from underneath.
//
// Distinct from Pylon Grid (columns standing on the ground), Gate Corridor
// (frames receding head-on) and Neon City (a skyline at eye level): this is the
// only scene in the library whose subject is ABOVE the camera. Two parallel
// chords run away toward the horizon with diagonal bracing zigzagging between
// them, and the whole thing is looked up at, so the perspective converges
// downward rather than inward.
//
// Joint plates are stamped in the track's signature form.
//
// Scaffolded by tools/new-scene.js. See HOWTO-visualizers.md.

export const girderLattice = {
    name: 'Girder Lattice',
    family: 'structural',
    kind: 'fragment',
    // Crisp line work: the track's surface grain would only fur the edges.
    texture: 0,
    consumes: ['cast', 'ink'],
    traits: ['shape', 'camera', 'space', 'style'],

    params: {
        bays:     { type: 'int',   range: [3, 16],     default: 8,    uniform: 'u_bays', bias: 'density' },
        beam:     { type: 'float', range: [0.01, 0.12],default: 0.04, uniform: 'u_beam' },
        depth:    { type: 'float', range: [0.3, 2.2],  default: 1.0,  uniform: 'u_depth' },
        brace:    { type: 'float', range: [0, 1],      default: 0.7,  uniform: 'u_brace', bias: 'density' },
        travel:   { type: 'float', range: [0.0, 0.6],  default: 0.12, uniform: 'u_travel', bias: 'motion', rate: true },
        plate:    { type: 'float', range: [0, 0.12],   default: 0.05, uniform: 'u_plate' },
        lamp:     { type: 'float', range: [0, 1.4],    default: 0.45, uniform: 'u_lamp', bias: 'energy' },
        palette:  { type: 'palette', count: 5 },
    },

    reactive: {
        lamp:  { feature: 'beat',     amount: 0.3,  response: 'spike' },
        brace: { feature: 'bandLow',  amount: 0.15, response: 'smooth' },
    },

    shader: `
// Distance to a line segment. The truss is nothing but segments.
float segment(vec2 p, vec2 a, vec2 b) {
    vec2 pa = p - a, ba = b - a;
    float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-6), 0.0, 1.0);
    return length(pa - ba * h);
}

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

    // Looking up: the vanishing point sits BELOW the frame's horizon, and the
    // structure spans above it. Everything converges toward that point.
    float vanishY = sigHorizonY() * 0.5 - 0.35;
    float above = p.y - vanishY;

    // Sky between the members.
    vec3 col = mix(pal(0) * 0.1, pal(1) * 0.06, sat(above * 0.6));

    if (above <= 0.001) {
        col = sigAir(col, p, 1.0);
        return vec4(inkValue(col), 1.0);
    }

    float best = 1e3;
    float nearest = 0.0;
    float plateHit = 1e3;

    // Bays march toward the camera on a wrapping saw, so the truss travels
    // overhead rather than sitting still.
    for (int i = 0; i < 16; i++) {
        if (i >= u_bays) break;
        float fi = float(i);
        float phase = fract(fi / float(u_bays) + t);

        // Perspective: nearer bays are further up the frame and further apart.
        float y0 = vanishY + exp(phase * 2.4) * 0.12 * u_depth;
        float y1 = vanishY + exp((phase + 1.0 / float(u_bays)) * 2.4) * 0.12 * u_depth;
        float halfW0 = (y0 - vanishY) * 1.5;
        float halfW1 = (y1 - vanishY) * 1.5;

        // The two chords, running away from the camera.
        best = min(best, segment(p, vec2(-halfW0, y0), vec2(-halfW1, y1)));
        best = min(best, segment(p, vec2(halfW0, y0), vec2(halfW1, y1)));

        // Cross member at this joint.
        best = min(best, segment(p, vec2(-halfW0, y0), vec2(halfW0, y0)));

        // Diagonal bracing, alternating direction bay to bay: the zigzag is
        // what makes it a truss rather than a ladder.
        if (u_brace > 0.05) {
            float flip = mod(fi, 2.0) < 0.5 ? 1.0 : -1.0;
            float d = segment(p, vec2(-halfW0 * flip, y0), vec2(halfW1 * flip, y1));
            best = min(best, d + (1.0 - u_brace) * 0.05);
        }

        if (u_plate > 0.004) {
            float pd = castMain((p - vec2(halfW0, y0)) / u_plate) * u_plate;
            plateHit = min(plateHit, pd);
            pd = castMain((p - vec2(-halfW0, y0)) / u_plate) * u_plate;
            plateHit = min(plateHit, pd);
        }

        if (phase > 0.75) nearest = max(nearest, phase);
    }

    // Members are drawn thicker nearer the camera, which is the only depth cue
    // a wireframe has.
    float weight = u_beam * (0.4 + above * 0.9) * (0.5 + u_sigLine);
    float steel = smoothstep(weight, weight * 0.3, best);

    col = mix(col, pal(2) * (0.3 + above * 0.5), steel);
    col += pal(3) * inkStroke(best - weight) * 0.4;

    // Joint plates.
    col = mix(col, pal(4) * 0.7, smoothstep(0.006, -0.006, plateHit));

    // A lamp slung under the nearest bay, pulsing on the beat. Local.
    col += pal(4) * exp(-length(p - vec2(0.0, vanishY + nearest * 1.4)) * 14.0) * u_lamp;

    col = sigAir(col, p, 1.0 - smoothstep(0.0, 1.4, above));
    return vec4(col, 1.0);
}
`,
};

export default girderLattice;
