|
| 1 | +struct Params { |
| 2 | + neighbor_dist: f32, |
| 3 | + separation_dist: f32, |
| 4 | + max_speed: f32, |
| 5 | + max_force: f32, |
| 6 | + home_radius: f32, |
| 7 | + _pad0: f32, |
| 8 | + _pad1: f32, |
| 9 | + _pad2: f32, |
| 10 | +} |
| 11 | + |
| 12 | +@group(0) @binding(0) var<storage, read_write> position: array<f32>; |
| 13 | +@group(0) @binding(1) var<storage, read_write> velocity: array<f32>; |
| 14 | +@group(0) @binding(2) var<storage, read_write> home: array<f32>; |
| 15 | +@group(0) @binding(3) var<storage, read_write> steer: array<f32>; |
| 16 | +@group(0) @binding(4) var<uniform> params: Params; |
| 17 | + |
| 18 | +fn limit(v: vec3<f32>, max_len: f32) -> vec3<f32> { |
| 19 | + let len = length(v); |
| 20 | + if len > max_len { return v * (max_len / len); } |
| 21 | + return v; |
| 22 | +} |
| 23 | + |
| 24 | +// Reynolds: steering = desired - velocity |
| 25 | +fn steer_toward(desired: vec3<f32>, vel: vec3<f32>) -> vec3<f32> { |
| 26 | + let len = length(desired); |
| 27 | + if len < 1e-6 { return vec3<f32>(0.0); } |
| 28 | + return limit(desired * (params.max_speed / len) - vel, params.max_force); |
| 29 | +} |
| 30 | + |
| 31 | +@compute @workgroup_size(64) |
| 32 | +fn main(@builtin(global_invocation_id) gid: vec3<u32>) { |
| 33 | + let i = gid.x; |
| 34 | + let count = arrayLength(&position) / 3u; |
| 35 | + if i >= count { return; } |
| 36 | + |
| 37 | + let pos = vec3<f32>(position[i * 3u], position[i * 3u + 1u], position[i * 3u + 2u]); |
| 38 | + let vel = vec3<f32>(velocity[i * 3u], velocity[i * 3u + 1u], velocity[i * 3u + 2u]); |
| 39 | + |
| 40 | + var separation = vec3<f32>(0.0); |
| 41 | + var alignment = vec3<f32>(0.0); |
| 42 | + var cohesion = vec3<f32>(0.0); |
| 43 | + var separation_count = 0u; |
| 44 | + var neighbor_count = 0u; |
| 45 | + |
| 46 | + for (var j = 0u; j < count; j = j + 1u) { |
| 47 | + if j == i { continue; } |
| 48 | + let other = vec3<f32>(position[j * 3u], position[j * 3u + 1u], position[j * 3u + 2u]); |
| 49 | + let d = distance(pos, other); |
| 50 | + if d > 0.0 && d < params.separation_dist { |
| 51 | + // Point away from the neighbor, weighted by closeness |
| 52 | + separation = separation + normalize(pos - other) / d; |
| 53 | + separation_count = separation_count + 1u; |
| 54 | + } |
| 55 | + if d < params.neighbor_dist { |
| 56 | + alignment = alignment |
| 57 | + + vec3<f32>(velocity[j * 3u], velocity[j * 3u + 1u], velocity[j * 3u + 2u]); |
| 58 | + cohesion = cohesion + other; |
| 59 | + neighbor_count = neighbor_count + 1u; |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + var force = vec3<f32>(0.0); |
| 64 | + if separation_count > 0u { |
| 65 | + force = force + steer_toward(separation / f32(separation_count), vel) * 1.5; |
| 66 | + } |
| 67 | + if neighbor_count > 0u { |
| 68 | + force = force + steer_toward(alignment, vel); |
| 69 | + force = force + steer_toward(cohesion / f32(neighbor_count) - pos, vel); |
| 70 | + } |
| 71 | + |
| 72 | + // The tether: inside home_radius the weight is < 1 and flocking wins; |
| 73 | + // past it the pull grows quadratically until it dominates everything. |
| 74 | + let home_pos = vec3<f32>(home[i * 3u], home[i * 3u + 1u], home[i * 3u + 2u]); |
| 75 | + let to_home = home_pos - pos; |
| 76 | + let w = length(to_home) / params.home_radius; |
| 77 | + force = force + steer_toward(to_home, vel) * min(w * w, 8.0); |
| 78 | + |
| 79 | + steer[i * 3u] = force.x; |
| 80 | + steer[i * 3u + 1u] = force.y; |
| 81 | + steer[i * 3u + 2u] = force.z; |
| 82 | +} |
0 commit comments