bevy_tiled_background 0.3.0

A Bevy plugin for creating tiled, animated UI backgrounds with rotation, staggering, and scrolling
Documentation
#import bevy_ui::ui_vertex_output::UiVertexOutput
#import bevy_render::globals::Globals

struct TiledMaterial {
    color: vec4<f32>,
    scale: f32,
    rotation: f32,
    stagger: f32,
    spacing: f32,
    scroll_speed: vec2<f32>,
}

@group(0) @binding(1) var<uniform> globals: Globals;
@group(1) @binding(0) var<uniform> material: TiledMaterial;
@group(1) @binding(1) var pattern_texture: texture_2d<f32>;
@group(1) @binding(2) var pattern_sampler: sampler;

@fragment
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
    // Use absolute pixel coordinates centered on the node
    let pos = (in.uv - 0.5) * in.size;

    // Rotate the coordinate space
    let s = sin(material.rotation);
    let c = cos(material.rotation);
    var rotated_pos = vec2<f32>(
        c * pos.x - s * pos.y,
        s * pos.x + c * pos.y
    );

    // Apply scrolling (scroll_speed is in pixels per second)
    rotated_pos += material.scroll_speed * globals.time;

    // Calculate tile dimensions based on scale (multiplier of native texture size)
    let tex_dims = textureDimensions(pattern_texture);
    let image_size = vec2<f32>(tex_dims) * material.scale;
    
    // Calculate gradients based on the continuous coordinate space to avoid seams at tile boundaries
    // and to support WebGPU which forbids implicit derivatives in non-uniform control flow.
    let uv_scale = 1.0 / image_size;
    let ddx = dpdx(rotated_pos) * uv_scale;
    let ddy = dpdy(rotated_pos) * uv_scale;

    // Cell size = image size + spacing (spacing is the gap in pixels between images)
    let cell_size = image_size + material.spacing;

    // Stagger rows for brick-like pattern
    let row = floor(rotated_pos.y / cell_size.y);
    rotated_pos.x += row * material.stagger * cell_size.x;

    // Position within the cell
    let cell_pos = rotated_pos - floor(rotated_pos / cell_size) * cell_size;

    // Check if we're within the image portion of the cell (not in the spacing gap)
    if (cell_pos.x >= 0.0 && cell_pos.x < image_size.x &&
        cell_pos.y >= 0.0 && cell_pos.y < image_size.y) {
        
        let sample_uv = cell_pos / image_size;
        let tex_color = textureSampleGrad(pattern_texture, pattern_sampler, sample_uv, ddx, ddy);
        return tex_color * material.color;
    }

    return vec4<f32>(0.0, 0.0, 0.0, 0.0);
}