const PI: f32 = 3.14159265358979323846264338327950288; struct Uniforms { /// The size of the canvas size: vec2, /// The length of the first horizontal segment len_start: f32, /// The length of the last horizontal segment len_end: f32, /// The inner half thickness thickness: f32, /// The thickness of the border (added to the inner thickness for total thickness) border: f32, /// Current time time: f32, /// The period of the pulse effect (in seconds) pulse_period: f32, /// The speed at which the pulse travels along the path (in units per second) pulse_speed: f32, /// The width of the pulse effect (in units) pulse_width: f32, /// The color of the border border_color: vec4, /// The color of the base of the track color_base: vec4, /// The color of the pulsing color_accent: vec4, }; @group(#{MATERIAL_BIND_GROUP}) @binding(0) var U: Uniforms; // Distance to segment fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { let pa = p - a; let ba = b - a; let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); return length(pa - ba * h); } fn sdArc( p: vec2, c: vec2, r: f32, a0: f32, a1: f32 ) -> f32 { let d = p - c; var ang = atan2(d.y, d.x); var s = a0; var e = a1; if (e < s) { e += 2.0 * PI; } if (ang < s) { ang += 2.0 * PI; } let on_arc = (ang >= s) && (ang <= e); let circle = abs(length(d) - r); let p0 = c + r * vec2(cos(s), sin(s)); let p1 = c + r * vec2(cos(e), sin(e)); let ends = min(length(p - p0), length(p - p1)); return select(ends, circle, on_arc); } // Compute SDF and normalized distance along path fn trackSDF(p: vec2) -> vec2 { let top_offset = U.thickness + U.border; let bottom_offset = U.size.y - top_offset; let midline = U.size.y / 2.0; let drop = midline - top_offset; let radius = drop / 2.0; // Construct points let p0 = vec2(U.size.x - U.len_start, top_offset); let p1 = vec2(U.size.x - radius, top_offset); let arc0_center = vec2(p1.x, drop / 2.0 + top_offset); let p2 = vec2(p1.x, midline); let p3 = vec2(radius, midline); let arc1_center = vec2(p3.x, drop / 2.0 + midline); let p4 = vec2(radius, bottom_offset); let p5 = vec2(U.len_end, bottom_offset); // Total length let L0 = length(p1 - p0); let L1 = PI * length(p2 - p1); let L2 = length(p3 - p2); let L3 = PI * length(p4 - p3); let L4 = length(p5 - p4); let total = L0 + L1 + L2 + L3 + L4; var best_d = 1e9; var best_t = 0.0; // Helper macro-like inline pattern // segment i with accumulated offset var accum = 0.0; // p0->p1 { let d = sdSegment(p, p0, p1); let ba = p1 - p0; let h = clamp(dot(p - p0, ba) / dot(ba, ba), 0.0, 1.0); let t = (accum + h * L0) / total; if (d < best_d) { best_d = d; best_t = t; } accum = accum + L0; } // ARC p1->p2 { let d = sdArc(p, arc0_center, radius, PI * 3.0 / 2.0, PI / 2.0); let ba = p2 - p1; let h = clamp(dot(p - p1, ba) / dot(ba, ba), 0.0, 1.0); let t = (accum + h * L1) / total; if (d < best_d) { best_d = d; best_t = t; } accum = accum + L1; } // p2->p3 (full width) { let d = sdSegment(p, p2, p3); let ba = p3 - p2; let h = clamp(dot(p - p2, ba) / dot(ba, ba), 0.0, 1.0); let t = (accum + h * L2) / total; if (d < best_d) { best_d = d; best_t = t; } accum = accum + L2; } // ARC p3->p4 { let d = sdArc(p, arc1_center, radius, PI / 2.0, PI * 3.0 / 2.0); let ba = p4 - p3; let h = clamp(dot(p - p3, ba) / dot(ba, ba), 0.0, 1.0); let t = (accum + h * L3) / total; if (d < best_d) { best_d = d; best_t = t; } accum = accum + L3; } // p4->p5 { let d = sdSegment(p, p4, p5); let ba = p5 - p4; let h = clamp(dot(p - p4, ba) / dot(ba, ba), 0.0, 1.0); let t = (accum + h * L4) / total; if (d < best_d) { best_d = d; best_t = t; } } return vec2(best_d, best_t); } @fragment fn fs_main(@builtin(position) frag_coord: vec4) -> @location(0) vec4 { let p = frag_coord.xy; let res = trackSDF(p); let d = res.x; let t = res.y; // bounces back and forth between 0.0 and 1.0 let t_wrap = abs(((t + U.time) % 2.0) - 1.0); // bounces back and forth between 0.0 and U.pulse_period let t_wrap_pulse = abs(((t - U.pulse_speed * U.time) % (U.pulse_period * 2.0)) - U.pulse_period); let inner = U.thickness; let outer = U.thickness + U.border; let aa = 1.0; let inner_a = smoothstep(inner + aa, inner - aa, d); let outer_a = smoothstep(outer + aa, outer - aa, d); let border_mask = clamp(outer_a - inner_a, 0.0, 1.0); // let color_mix = smoothstep(-0.1, 0.0, -abs(t - t_wrap_pulse)); let color_mix = smoothstep(-U.pulse_width, 0.0, -abs(t - t_wrap)); let inner_color = mix(U.color_base, U.color_accent, color_mix); // let inner_color = mix(U.color_base, U.color_accent, d); let color = inner_color * inner_a + U.border_color * border_mask; let alpha = max(inner_a, outer_a); return vec4(color.rgb, alpha); // return vec4(d / 3000.0, t / 3000.0, 0.0, 1.0); // return vec4(1.0, 0.0, 0.0, 1.0); }