pub const ORDER1: [(i64, i64, i64); 6] = [
(1, 0, 0),
(-1, 0, 0),
(0, 1, 0),
(0, -1, 0),
(0, 0, 1),
(0, 0, -1),
];
pub const ORDER2: [(i64, i64, i64); 18] = [
(1, 0, 0),
(-1, 0, 0),
(0, 1, 0),
(0, -1, 0),
(0, 0, 1),
(0, 0, -1),
(1, 1, 0),
(1, -1, 0),
(-1, 1, 0),
(-1, -1, 0),
(1, 0, 1),
(1, 0, -1),
(-1, 0, 1),
(-1, 0, -1),
(0, 1, 1),
(0, 1, -1),
(0, -1, 1),
(0, -1, -1),
];
pub const ORDER3: [(i64, i64, i64); 26] = {
let mut out = [(0i64, 0i64, 0i64); 26];
let mut index = 0;
let mut dz = -1i64;
while dz <= 1 {
let mut dy = -1i64;
while dy <= 1 {
let mut dx = -1i64;
while dx <= 1 {
if dx != 0 || dy != 0 || dz != 0 {
out[index] = (dx, dy, dz);
index += 1;
}
dx += 1;
}
dy += 1;
}
dz += 1;
}
out
};
#[must_use]
pub fn offsets_for(order: u8, depth: usize) -> Vec<(i64, i64, i64)> {
let all: &[(i64, i64, i64)] = match order {
1 => &ORDER1,
2 => &ORDER2,
_ => &ORDER3,
};
if depth > 1 {
return all.to_vec();
}
let mut planar: Vec<(i64, i64, i64)> = all.iter().copied().filter(|o| o.2 == 0).collect();
if order >= 2 {
planar = ORDER3
.iter()
.copied()
.filter(|o| o.2 == 0)
.collect::<Vec<_>>();
}
planar
}
#[inline]
#[must_use]
fn wrap(v: i64, span: usize) -> usize {
let span = span as i64;
if v >= 0 {
if v < span {
v as usize
} else if v < 2 * span {
(v - span) as usize
} else {
(v % span) as usize
}
} else if v >= -span {
(v + span) as usize
} else {
v.rem_euclid(span) as usize
}
}
#[derive(Clone, Debug)]
pub struct Lattice {
pub width: usize,
pub height: usize,
pub depth: usize,
pub labels: Vec<u32>,
pub offsets: Vec<(i64, i64, i64)>,
}
impl Lattice {
#[must_use]
pub fn medium(width: usize, height: usize, depth: usize, order: u8) -> Self {
Self {
width,
height,
depth,
labels: vec![0; width * height * depth.max(1)],
offsets: offsets_for(order, depth),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.labels.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.labels.is_empty()
}
pub fn tile(&mut self, side: usize) -> u32 {
self.tile_grid(
side,
self.width / side,
self.height / side,
(self.depth / side).max(1),
)
}
pub fn tile_grid(&mut self, side: usize, nx: usize, ny: usize, nz: usize) -> u32 {
let mut next = 1u32;
let nx = nx.min(self.width / side);
let ny = ny.min(self.height / side);
let nz = if self.depth == 1 {
1
} else {
nz.min(self.depth / side)
};
let side_z = if self.depth == 1 { 1 } else { side };
for bz in 0..nz {
for by in 0..ny {
for bx in 0..nx {
for z in bz * side_z..(bz + 1) * side_z {
for y in by * side..(by + 1) * side {
for x in bx * side..(bx + 1) * side {
let index = self.index(x as i64, y as i64, z as i64);
self.labels[index] = next;
}
}
}
next += 1;
}
}
}
next - 1
}
#[must_use]
pub fn index(&self, x: i64, y: i64, z: i64) -> usize {
(wrap(z, self.depth) * self.height + wrap(y, self.height)) * self.width
+ wrap(x, self.width)
}
#[must_use]
pub fn coords(&self, index: usize) -> (i64, i64, i64) {
let plane = self.width * self.height;
let z = index / plane;
let rest = index % plane;
(
(rest % self.width) as i64,
(rest / self.width) as i64,
z as i64,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrapping_agrees_with_the_remainder_everywhere_it_is_asked() {
for span in [1usize, 2, 5, 8] {
for v in -3 * span as i64..3 * span as i64 {
assert_eq!(
wrap(v, span),
v.rem_euclid(span as i64) as usize,
"wrap({v}, {span})"
);
}
}
}
#[test]
fn indexing_wraps_on_every_axis() {
let l = Lattice::medium(8, 4, 2, 2);
assert_eq!(l.index(0, 0, 0), 0);
assert_eq!(l.index(8, 4, 2), 0);
assert_eq!(l.index(-1, -1, -1), l.len() - 1);
}
#[test]
fn coordinates_round_trip_through_the_index() {
let l = Lattice::medium(5, 4, 3, 2);
for index in 0..l.len() {
let (x, y, z) = l.coords(index);
assert_eq!(l.index(x, y, z), index);
}
}
#[test]
fn a_plane_takes_the_eight_moore_neighbours_at_order_two() {
assert_eq!(offsets_for(2, 1).len(), 8);
assert_eq!(offsets_for(1, 1).len(), 4);
assert_eq!(offsets_for(3, 1).len(), 8);
}
#[test]
fn a_volume_takes_its_own_counts() {
assert_eq!(offsets_for(1, 4).len(), 6);
assert_eq!(offsets_for(2, 4).len(), 18);
assert_eq!(offsets_for(3, 4).len(), 26);
}
#[test]
fn tiling_lays_down_equal_cells_in_the_plane() {
let mut l = Lattice::medium(12, 8, 1, 2);
assert_eq!(l.tile(4), 6);
let mut counts = std::collections::BTreeMap::new();
for &label in &l.labels {
*counts.entry(label).or_insert(0usize) += 1;
}
assert!(
!counts.contains_key(&0),
"the tiling should fill the lattice"
);
assert!(counts.values().all(|&v| v == 16), "{counts:?}");
}
#[test]
fn tiling_lays_down_equal_cells_in_a_volume() {
let mut l = Lattice::medium(8, 8, 8, 2);
assert_eq!(l.tile(4), 8);
let mut counts = std::collections::BTreeMap::new();
for &label in &l.labels {
*counts.entry(label).or_insert(0usize) += 1;
}
assert!(counts.values().all(|&v| v == 64), "{counts:?}");
}
}