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;
}