#define_import_path henad::space
// World geometry, the twin of `henad_core::authoring::primitives::space`.
//
// Integer results are bit-equal to the Rust side. Float results agree to a tolerance instead, since
// WGSL's float `%` is defined through a division while Rust's is an exact fmod.
// Boundary codes, matching `Boundary::Torus` and `Boundary::Bounded`.
const TORUS: u32 = 0u;
const BOUNDED: u32 = 1u;
// Neighbour tables, matching the Rust consts of the same names.
const MOORE_ROW_MAJOR: u32 = 0u;
const MOORE_COLUMN_MAJOR: u32 = 1u;
const VON_NEUMANN: u32 = 2u;
// Wraps `v` into `0..m`. Undefined for `m == 0`, where the Rust side panics.
fn wrap_index(v: i32, m: i32) -> i32 {
return ((v % m) + m) % m;
}
// Wraps `v` into `0.0..world`. A `v` a little below 0.0 whose wrap rounds up to `world` wraps to 0.0.
fn wrap_coord(v: f32, world: f32) -> f32 {
let r = v % world;
let wrapped = select(r, r + abs(world), r < 0.0);
return select(wrapped, 0.0, wrapped == world);
}
// Flat index of cell `(x, y)` in a row-major grid `w` wide.
fn cell_index(x: u32, y: u32, w: u32) -> u32 {
return y * w + x;
}
// The cell `(dx, dy)` away from `(x, y)`. `.z` is non-zero when that cell exists, since WGSL has no
// `Option`. `dy` runs south.
fn offset_cell(x: u32, y: u32, dx: i32, dy: i32, w: u32, h: u32, boundary: u32) -> vec3<i32> {
let nx = i32(x) + dx;
let ny = i32(y) + dy;
if (boundary == TORUS) {
return vec3<i32>(wrap_index(nx, i32(w)), wrap_index(ny, i32(h)), 1);
}
if (nx < 0 || ny < 0 || nx >= i32(w) || ny >= i32(h)) {
return vec3<i32>(0, 0, 0);
}
return vec3<i32>(nx, ny, 1);
}
// Shortest signed delta from `a` to `b`, in `[-world/2, world/2)` under `TORUS`.
fn axis_delta(a: f32, b: f32, world: f32, boundary: u32) -> f32 {
let d = b - a;
if (boundary == BOUNDED) {
return d;
}
// Both positions must be within one world of each other, as on the Rust side. Two independent
// compares rather than a `%`, because this runs per neighbour per agent.
let half = world * 0.5;
var c = d;
if (c >= half) {
c = c - world;
}
if (c < -half) {
c = c + world;
}
return c;
}
// Squared distance between two points. A point is a `vec2`, as the agent buffers pack it, where the
// Rust side takes four scalars from its struct-of-arrays lanes.
fn dist_sq(a: vec2<f32>, b: vec2<f32>, world: vec2<f32>, boundary: u32) -> f32 {
let d = vec2<f32>(
axis_delta(a.x, b.x, world.x, boundary),
axis_delta(a.y, b.y, world.y, boundary),
);
return dot(d, d);
}
// Number of neighbours in a table.
fn neighbor_count(table: u32) -> u32 {
return select(8u, 4u, table == VON_NEUMANN);
}
// Neighbour `n` of a table, in the same order as the Rust const of that name.
//
// The Moore tables are the 3x3 block with the centre skipped, so the index steps over 4.
fn neighbor_offset(table: u32, n: u32) -> vec2<i32> {
if (table == VON_NEUMANN) {
if (n == 0u) { return vec2<i32>(0, -1); }
if (n == 1u) { return vec2<i32>(-1, 0); }
if (n == 2u) { return vec2<i32>(1, 0); }
return vec2<i32>(0, 1);
}
let k = n + select(0u, 1u, n >= 4u);
let lo = i32(k % 3u) - 1;
let hi = i32(k / 3u) - 1;
if (table == MOORE_COLUMN_MAJOR) {
return vec2<i32>(hi, lo);
}
return vec2<i32>(lo, hi);
}
// Heading as one of eight octants, clockwise from east because the display's y axis points down.
fn heading_octant(vx: f32, vy: f32) -> u32 {
let east = vx >= 0.0;
let south = vy >= 0.0;
let steep = abs(vy) > abs(vx);
if (east && south && !steep) { return 0u; }
if (east && south && steep) { return 1u; }
if (!east && south && steep) { return 2u; }
if (!east && south && !steep) { return 3u; }
if (!east && !south && !steep) { return 4u; }
if (!east && !south && steep) { return 5u; }
if (east && !south && steep) { return 6u; }
return 7u;
}