#[inline]
pub fn hilbert_to_zxy(id: u64) -> (u8, u32, u32) {
let z = (((u64::BITS - (3 * id + 1).leading_zeros()) - 1) / 2) as u8;
let acc = ((1 << (z * 2)) - 1) / 3;
let mut pos = id - acc;
let (x, y) = (0..z).fold((0, 0), |(x, y), a| {
let s = 1u32 << a;
let rx = s & (pos as u32 >> 1);
let ry = s & (pos as u32 ^ rx);
let (x, y) = rotate(s, x, y, rx, ry);
pos >>= 1;
(x + rx, y + ry)
});
(z, x, y)
}
#[inline]
pub fn zxy_to_hilbert(z: u8, mut x: u32, mut y: u32) -> u64 {
let acc = ((1 << (z * 2)) - 1) / 3;
(0..z).rev().fold(acc, |acc, a| {
let s = 1 << a;
let rx = s & x;
let ry = s & y;
(x, y) = rotate(s, x, y, rx, ry);
acc + ((((3 * rx) ^ ry) as u64) << a)
})
}
#[inline]
const fn rotate(n: u32, mut x: u32, mut y: u32, rx: u32, ry: u32) -> (u32, u32) {
if ry == 0 {
if rx != 0 {
x = (n - 1).wrapping_sub(x);
y = (n - 1).wrapping_sub(y);
}
(x, y) = (y, x)
}
(x, y)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip() {
let fixture = vec![
((0, 0, 0), 0),
((1, 0, 0), 1),
((1, 0, 1), 2),
((1, 1, 1), 3),
((1, 1, 0), 4),
((2, 0, 1), 8),
((2, 1, 1), 7),
((2, 2, 0), 19),
((2, 3, 3), 15),
((2, 3, 2), 16),
((3, 0, 0), 21),
((3, 7, 0), 84),
((4, 0, 0), 85),
((4, 15, 0), 340),
((18, 1, 1), 22906492247),
((31, 100, 100), 1537228672809139573),
];
for ((z, x, y), expected_tile_id) in fixture {
let tile_id = zxy_to_hilbert(z, x, y);
assert_eq!(tile_id, expected_tile_id);
assert_eq!(hilbert_to_zxy(tile_id), (z, x, y));
}
}
}