nightshade 0.13.2

A cross-platform data-oriented game engine.
Documentation
struct InteractionUniforms {
    interactor_count: u32,
    bend_map_size: u32,
    world_size: f32,
    world_center_x: f32,
    world_center_z: f32,
    decay_rate: f32,
    delta_time: f32,
    _padding: f32,
}

struct Interactor {
    position: vec3<f32>,
    radius: f32,
    velocity: vec3<f32>,
    strength: f32,
}

struct GrassInstance {
    position: vec3<f32>,
    rotation: f32,
    height: f32,
    width: f32,
    species_index: u32,
    lod: u32,
    base_color: vec4<f32>,
    tip_color: vec4<f32>,
    bend: vec2<f32>,
}

@group(0) @binding(0) var<uniform> uniforms: InteractionUniforms;
@group(0) @binding(1) var<storage, read> interactors: array<Interactor>;
@group(0) @binding(2) var bend_map_read: texture_2d<f32>;
@group(0) @binding(3) var bend_map_write: texture_storage_2d<rg32float, write>;

@group(1) @binding(0) var<storage, read_write> instances: array<GrassInstance>;
@group(1) @binding(1) var bend_map_sampler: sampler;

fn world_to_bend_uv(world_pos: vec2<f32>) -> vec2<f32> {
    let half_size = uniforms.world_size * 0.5;
    let local_x = world_pos.x - uniforms.world_center_x;
    let local_z = world_pos.y - uniforms.world_center_z;

    return vec2<f32>(
        (local_x + half_size) / uniforms.world_size,
        (local_z + half_size) / uniforms.world_size
    );
}

fn bend_uv_to_pixel(uv: vec2<f32>) -> vec2<i32> {
    return vec2<i32>(
        i32(uv.x * f32(uniforms.bend_map_size)),
        i32(uv.y * f32(uniforms.bend_map_size))
    );
}

@compute @workgroup_size(16, 16)
fn update_bend_map(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let pixel = vec2<i32>(global_id.xy);

    if pixel.x >= i32(uniforms.bend_map_size) || pixel.y >= i32(uniforms.bend_map_size) {
        return;
    }

    let uv = vec2<f32>(
        (f32(pixel.x) + 0.5) / f32(uniforms.bend_map_size),
        (f32(pixel.y) + 0.5) / f32(uniforms.bend_map_size)
    );

    let half_size = uniforms.world_size * 0.5;
    let world_x = uniforms.world_center_x + (uv.x - 0.5) * uniforms.world_size;
    let world_z = uniforms.world_center_z + (uv.y - 0.5) * uniforms.world_size;

    var current_bend = textureLoad(bend_map_read, pixel, 0).xy;

    current_bend *= (1.0 - uniforms.decay_rate * uniforms.delta_time);

    for (var index = 0u; index < uniforms.interactor_count; index++) {
        let interactor = interactors[index];

        let dx = world_x - interactor.position.x;
        let dz = world_z - interactor.position.z;
        let distance = sqrt(dx * dx + dz * dz);

        if distance < interactor.radius {
            let falloff = 1.0 - (distance / interactor.radius);
            let falloff_smooth = falloff * falloff * (3.0 - 2.0 * falloff);

            var bend_direction: vec2<f32>;
            if distance > 0.001 {
                bend_direction = vec2<f32>(dx, dz) / distance;
            } else {
                bend_direction = normalize(interactor.velocity.xz);
            }

            let velocity_influence = length(interactor.velocity.xz) * 0.3;
            let bend_strength = interactor.strength * falloff_smooth * (1.0 + velocity_influence);

            current_bend += bend_direction * bend_strength * uniforms.delta_time * 3.0;
        }
    }

    let max_bend = 0.5;
    let bend_length = length(current_bend);
    if bend_length > max_bend {
        current_bend = current_bend * (max_bend / bend_length);
    }

    textureStore(bend_map_write, pixel, vec4<f32>(current_bend, 0.0, 1.0));
}

@compute @workgroup_size(256)
fn sample_bend_for_instances(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let instance_index = global_id.x;

    if instance_index >= arrayLength(&instances) {
        return;
    }

    let instance = instances[instance_index];

    if instance.height <= 0.0 {
        return;
    }

    let uv = world_to_bend_uv(instance.position.xz);

    if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 {
        instances[instance_index].bend = vec2<f32>(0.0, 0.0);
        return;
    }

    let bend = textureSampleLevel(bend_map_read, bend_map_sampler, uv, 0.0).xy;

    instances[instance_index].bend = bend;
}