// Structural family: a suspension bridge passing overhead — two towers in // frame at a time, the main cable slung between them, hangers dropping to a // deck that runs off both sides. // // Girder Lattice is a truss: hundreds of short members in tension and // compression, and the image is a texture of triangles. This is the opposite // structure and reads as one — two long curves and a rhythm of verticals under // them, with almost nothing else in the frame. The curve is the whole subject, // and the only thing that repeats is the spacing. export const suspensionSpan = { name: 'Suspension Span', family: 'structural', kind: 'fragment', // Paints 21% of the frame — see checks/phase12 coverage. surface: 'composable', texture: 0.6, consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { span: { type: 'float', range: [0.5, 2.2], default: 1.1, uniform: 'u_span', bias: 'density' }, sag: { type: 'float', range: [0.15, 0.9], default: 0.5, uniform: 'u_sag' }, tower: { type: 'float', range: [0.3, 1.1], default: 0.7, uniform: 'u_tower' }, hangers: { type: 'float', range: [2, 22], default: 9, uniform: 'u_hangers', bias: 'density' }, deck: { type: 'float', range: [-0.7, 0.25], default: -0.2, uniform: 'u_deck', slowAxis: true }, lamp: { type: 'float', range: [0, 0.12], default: 0.045, uniform: 'u_lamp' }, travel: { type: 'float', range: [0.01, 0.3], default: 0.06, uniform: 'u_travel', bias: 'motion', rate: true }, palette: { type: 'palette', count: 5 }, }, reactive: { lamp: { feature: 'beat', amount: 0.2, response: 'spike' }, }, shader: ` // Distance to a segment, for the one-off members that are not on a grid. float seg(vec2 q, vec2 a, vec2 b) { vec2 ab = b - a, ap = q - a; float h = sat(dot(ap, ab) / max(dot(ab, ab), 1e-6)); return length(ap - ab * h); } vec4 scene(vec2 uv, vec2 p) { float t = u_time * u_travel + u_seed; p = sigCamera(p); float horizon = sigHorizonY() * 0.3; float deckY = horizon + u_deck; // Sky, and the water or ground under the deck. vec3 col = mix(pal(1) * 0.2, pal(0) * 0.06, sat((p.y - horizon) * 0.6 + 0.25)); col += pal(3) * exp(-abs(p.y - horizon) * 7.0) * 0.2; col = mix(col, pal(0) * 0.09, smoothstep(deckY + 0.01, deckY - 0.03, p.y)); // Bays: the bridge repeats every span, and travelling means the whole // structure slides through that repeat. float g = (p.x + t) / u_span; float bay = fract(g) - 0.5; float bayId = floor(g); float x = bay * u_span; // metres from this bay's centre float towerTop = deckY + u_tower; float w = 0.006 + u_sigLine * 0.02; // Main cable: a parabola from tower top to tower top, lowest mid-bay. float cableY = towerTop - u_sag * u_tower * (1.0 - 4.0 * bay * bay); float dCable = abs(p.y - cableY); col += palRamp(0.15) * smoothstep(w * 1.6, w * 0.3, dCable) * (0.6 + 0.55 * 0.6); // Hangers: verticals from the cable down to the deck, on their own spacing. float hg = (p.x + t) * u_hangers / u_span; float hx = (fract(hg) - 0.5) * u_span / u_hangers; float hangerCableY = towerTop - u_sag * u_tower * (1.0 - 4.0 * pow(fract(g + (floor(hg) - hg + 0.5) / u_hangers) - 0.5, 2.0)); float inSpan = step(p.y, hangerCableY) * step(deckY, p.y); col += palRamp(0.3) * smoothstep(w, w * 0.25, abs(hx)) * inSpan * (0.3 + 0.55 * 0.35); // Deck: one heavy line all the way across, which is what stops the cable // reading as a piece of string. col += pal(4) * smoothstep(w * 2.5, w * 0.6, abs(p.y - deckY)) * (0.4 + 0.55 * 0.5); col += pal(4) * inkStroke(abs(p.y - deckY) - w * 3.0) * 0.25; // Towers: a pair of legs and a crossbeam, with the signature form as the // lamp on top — the only object in the frame that is not a line. float dTower = min(seg(vec2(x, p.y), vec2(0.0, deckY - 0.1), vec2(0.0, towerTop)), seg(vec2(x, p.y), vec2(-0.03, towerTop - 0.12), vec2(0.03, towerTop - 0.12))); col += palRamp(0.05) * smoothstep(w * 3.0, w * 1.2, dTower) * 0.7; if (u_lamp > 0.004) { float d = castMain((vec2(x, p.y) - vec2(0.0, towerTop + u_lamp)) / u_lamp) * u_lamp; col += pal(3) * smoothstep(0.004, -0.004, d) * (0.6 + 0.55); col += pal(3) * inkStroke(d) * 0.6; } // Every other bay is a little further off, so the line of the bridge has // some air in it rather than being a flat elevation drawing. col = sigAir(col, p, smoothstep(0.2, 1.4, abs(bay) * 0.7 + hash11(bayId) * 0.5)); return vec4(inkValue(col), 1.0); } `, }; export default suspensionSpan;