bevy_sprinkles_editor 0.2.0

GPU particle system editor for Bevy
#import bevy_ui::ui_vertex_output::UiVertexOutput
#import bevy_ui_render::color_space::srgb_to_linear_rgb
#import bevy_sprinkles_editor::common::{checkerboard, rounded_box_sdf}

const MAX_STOPS: u32 = 8u;

struct GradientUniforms {
    border_radius: f32,
    checkerboard_size: f32,
    stop_count: u32,
    interpolation: u32,
    positions: array<vec4<f32>, 2>,
    colors: array<vec4<f32>, 8>,
}

@group(1) @binding(0)
var<uniform> uniforms: GradientUniforms;

fn get_stop_position(index: u32) -> f32 {
    let arr_idx = index / 4u;
    let vec_idx = index % 4u;
    if arr_idx == 0u {
        return uniforms.positions[0][vec_idx];
    }
    return uniforms.positions[1][vec_idx];
}

fn get_stop_color(index: u32) -> vec4<f32> {
    return uniforms.colors[index];
}

fn smoothstep_interp(edge0: f32, edge1: f32, x: f32) -> f32 {
    let t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
    return t * t * (3.0 - 2.0 * t);
}

fn sample_gradient(t: f32) -> vec4<f32> {
    if uniforms.stop_count == 0u {
        return vec4<f32>(1.0);
    }
    if uniforms.stop_count == 1u {
        return get_stop_color(0u);
    }

    let clamped_t = clamp(t, 0.0, 1.0);

    var left_idx = 0u;
    var right_idx = uniforms.stop_count - 1u;

    for (var i = 0u; i < uniforms.stop_count; i = i + 1u) {
        if get_stop_position(i) <= clamped_t {
            left_idx = i;
        }
    }

    for (var i = 0u; i < uniforms.stop_count; i = i + 1u) {
        if get_stop_position(i) >= clamped_t {
            right_idx = i;
            break;
        }
    }

    let left_pos = get_stop_position(left_idx);
    let right_pos = get_stop_position(right_idx);
    let left_color = get_stop_color(left_idx);
    let right_color = get_stop_color(right_idx);

    if left_idx == right_idx {
        return left_color;
    }

    let segment_range = right_pos - left_pos;
    if segment_range <= 0.0 {
        return left_color;
    }

    let local_t = (clamped_t - left_pos) / segment_range;

    var interp_t: f32;
    switch uniforms.interpolation {
        case 0u: {
            interp_t = 0.0;
        }
        case 1u: {
            interp_t = local_t;
        }
        case 2u: {
            interp_t = smoothstep_interp(0.0, 1.0, local_t);
        }
        default: {
            interp_t = local_t;
        }
    }

    return mix(left_color, right_color, interp_t);
}

@fragment
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
    let gradient_color = sample_gradient(in.uv.x);

    let checker_color_light = vec3<f32>(1.0, 1.0, 1.0);
    let checker_color_dark = srgb_to_linear_rgb(vec3<f32>(0.8, 0.8, 0.8));

    let cell_count = in.size.x / uniforms.checkerboard_size;
    let checker = checkerboard(in.uv, cell_count);
    let checker_rgb = mix(checker_color_dark, checker_color_light, checker);

    let final_rgb = mix(checker_rgb, srgb_to_linear_rgb(gradient_color.rgb), gradient_color.a);

    let pixel_pos = (in.uv - 0.5) * in.size;
    let half_size = in.size * 0.5;
    let d = rounded_box_sdf(pixel_pos, half_size, uniforms.border_radius);
    let mask_alpha = 1.0 - smoothstep(-1.0, 1.0, d);

    return vec4<f32>(final_rgb, mask_alpha);
}