use std::cmp::Ordering;
pub const MAX_DEPTH: u8 = 60;
pub trait LodVec<const N: usize>:
std::hash::Hash + Eq + Sized + Copy + Clone + Send + Sync + std::fmt::Debug + PartialOrd
{
const MAX_CHILDREN: usize = 1 << N;
fn get_child(self, index: usize) -> Self;
fn get_child_index(self, child: Self) -> usize;
fn contains_child_node(self, child: Self) -> bool;
fn root() -> Self;
fn can_subdivide(self, node: Self, detail: u32) -> bool;
fn is_inside_bounds(self, min: Self, max: Self, max_depth: u8) -> bool;
fn depth(self) -> u8;
}
pub trait ReasonableIntegerLike:
Default
+ Copy
+ core::cmp::Eq
+ core::cmp::Ord
+ std::marker::Send
+ std::marker::Sync
+ std::fmt::Debug
+ std::hash::Hash
{
fn fromusize(value: usize) -> Self;
fn tousize(self) -> usize;
}
#[macro_export]
macro_rules! reasonable_int_impl {
( $x:ty ) => {
impl ReasonableIntegerLike for $x {
#[inline(always)]
fn fromusize(value: usize) -> Self {
value as $x
}
#[inline(always)]
fn tousize(self) -> usize {
self as usize
}
}
};
}
reasonable_int_impl!(u8);
reasonable_int_impl!(u16);
reasonable_int_impl!(u32);
reasonable_int_impl!(u64);
#[derive(Debug, Copy, Clone, PartialEq, Eq, std::hash::Hash)]
pub struct CoordVec<const N: usize, DT = u8>
where
DT: ReasonableIntegerLike,
{
pub pos: [DT; N],
pub depth: u8,
}
impl<const N: usize, DT> CoordVec<N, DT>
where
DT: ReasonableIntegerLike,
{
#[inline(always)]
pub fn new(pos: [DT; N], depth: u8) -> Self {
debug_assert!(depth <= MAX_DEPTH);
debug_assert!(
pos.iter().all(|e| { e.tousize() < (1 << depth) }),
"All components of position should be < 2^depth"
);
Self { pos, depth }
}
#[inline(always)]
pub fn from_float_coords(pos: [f32; N], depth: u8) -> Self {
let scale_factor = (1 << depth) as f32;
Self {
pos: pos.map(|e| DT::fromusize((e * scale_factor) as usize)),
depth,
}
}
#[inline(always)]
pub fn float_coords(self) -> [f32; N] {
let scale_factor = 1.0 / (1 << self.depth) as f32;
self.pos.map(|e| e.tousize() as f32 * scale_factor)
}
#[inline(always)]
pub fn float_size(self) -> f32 {
1.0 / (1 << self.depth) as f32
}
}
impl<const N: usize, DT> LodVec<N> for CoordVec<N, DT>
where
DT: ReasonableIntegerLike,
{
#[inline(always)]
fn root() -> Self {
Self {
pos: [DT::default(); N],
depth: 0,
}
}
#[inline(always)]
fn depth(self) -> u8 {
self.depth
}
#[inline(always)]
fn get_child(self, index: usize) -> Self {
debug_assert!(index < <CoordVec<N> as LodVec<N>>::MAX_CHILDREN);
let mut new = Self::root();
for i in 0..N {
let p_doubled = self.pos[i].tousize() << 1;
let p = p_doubled + ((index & (1 << i)) >> i);
new.pos[i] = DT::fromusize(p);
}
new.depth = self.depth + 1;
debug_assert!(new.depth < MAX_DEPTH);
new
}
#[inline]
fn get_child_index(self, child: Self) -> usize {
debug_assert!(self.depth < child.depth);
let level_difference = child.depth - self.depth;
let mut idx: usize = 0;
for i in 0..N {
let sp = self.pos[i].tousize() << level_difference;
let pi = (child.pos[i].tousize() - sp) >> (level_difference - 1);
idx |= pi << i;
}
idx
}
#[inline]
fn contains_child_node(self, child: Self) -> bool {
if self.depth >= child.depth {
return false;
}
let level_difference = child.depth as isize - self.depth as isize;
self.pos
.iter()
.zip(child.pos)
.all(|(s, c)| s.tousize() == (c.tousize() >> level_difference))
}
#[inline(always)]
fn is_inside_bounds(self, min: Self, max: Self, max_depth: u8) -> bool {
let level = *[self.depth, min.depth, max.depth]
.iter()
.min()
.expect("Starting array not empty") as isize;
let self_difference: isize = self.depth as isize - level;
let min_difference: isize = min.depth as isize - level;
let max_difference: isize = max.depth as isize - level;
let self_lowered = self.pos.iter().map(|e| e.tousize() >> self_difference);
let min_lowered = min.pos.iter().map(|e| e.tousize() >> min_difference);
let max_lowered = max.pos.iter().map(|e| e.tousize() >> max_difference);
self.depth <= max_depth
&& self_lowered
.zip(min_lowered.zip(max_lowered))
.all(|(slf, (min, max))| slf >= min && slf <= max)
}
#[inline(always)]
fn can_subdivide(self, node: Self, detail: u32) -> bool {
let detail = detail as usize;
if node.depth >= self.depth {
return false;
}
let level_difference = self.depth - node.depth;
let bb_size = (detail + 1) << level_difference;
let offset = 1 << level_difference;
let min = node.pos.iter().map(|e| {
let x = e.tousize();
(x << (level_difference + 1)).saturating_sub(bb_size - offset)
});
let max = node.pos.iter().map(|e| {
let x = e.tousize();
(x << (level_difference + 1)).saturating_add(bb_size + offset)
});
let minmax = min.zip(max);
let local = self.pos.iter().map(|e| e.tousize() << 1);
local.zip(minmax).all(|(c, (min, max))| {
min <= c && c < max
})
}
}
pub type OctVec<DT = u8> = CoordVec<3, DT>;
pub type QuadVec<DT = u8> = CoordVec<2, DT>;
impl<DT> OctVec<DT>
where
DT: ReasonableIntegerLike,
{
#[inline(always)]
pub fn build(x: DT, y: DT, z: DT, depth: u8) -> Self {
Self::new([x, y, z], depth)
}
}
impl<DT> QuadVec<DT>
where
DT: ReasonableIntegerLike,
{
#[inline(always)]
pub fn build(x: DT, y: DT, depth: u8) -> Self {
Self::new([x, y], depth)
}
}
impl<const N: usize, DT> PartialOrd for CoordVec<N, DT>
where
DT: ReasonableIntegerLike,
{
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.depth != other.depth {
return None;
}
if self.pos == other.pos {
return Some(Ordering::Equal);
}
if self.pos.iter().zip(other.pos).all(|(s, o)| *s < o) {
return Some(Ordering::Less);
} else if self.pos.iter().zip(other.pos).all(|(s, o)| *s > o) {
return Some(Ordering::Greater);
}
None
}
}
impl<const N: usize, DT> std::ops::Add for CoordVec<N, DT>
where
DT: ReasonableIntegerLike + std::ops::AddAssign,
{
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
debug_assert_eq!(self.depth, rhs.depth);
let mut res = self;
for (e1, e2) in res.pos.iter_mut().zip(rhs.pos) {
*e1 += e2;
}
res
}
}
impl<const N: usize, DT> Default for CoordVec<N, DT>
where
DT: ReasonableIntegerLike,
{
#[inline]
fn default() -> Self {
Self::root()
}
}
#[inline]
pub fn get_chunk_count_at_max_depth<const N: usize>(a: CoordVec<N>, b: CoordVec<N>) -> usize {
assert_eq!(a.depth, b.depth);
b.pos
.iter()
.zip(a.pos)
.fold(1, |acc, (e1, e2)| acc * (e1 - e2 + 1) as usize)
}
#[cfg(feature = "rand")]
#[inline]
pub fn rand_cv<const N: usize, R: rand::Rng, T>(
rng: &mut R,
min: CoordVec<N, T>,
max: CoordVec<N, T>,
) -> CoordVec<N, T>
where
T: ReasonableIntegerLike + rand::distr::uniform::SampleUniform,
{
debug_assert_eq!(min.depth, max.depth);
let mut zz = [T::fromusize(0); N];
#[allow(clippy::needless_range_loop)]
for i in 0..N {
zz[i] = rng.random_range(min.pos[i]..max.pos[i]);
}
CoordVec::new(zz, min.depth)
}
#[cfg(test)]
mod tests {
use crate::coords::*;
use std::mem::size_of;
#[test]
fn sizes() {
assert_eq!(3, size_of::<QuadVec>());
assert_eq!(4, size_of::<OctVec>());
}
#[test]
fn find_child_idx() {
let z = OctVec::<u8>::root();
for i in 0..OctVec::<u8>::MAX_CHILDREN {
let c = z.get_child(i);
let ci = z.get_child_index(c);
assert_eq!(ci, i);
for j in 0..OctVec::<u8>::MAX_CHILDREN {
let cc = c.get_child(j);
let cci = c.get_child_index(cc);
println!("{}->{} ({}->{}): {:?}->{:?} ", i, j, ci, cci, c, cc);
assert_eq!(cci, j);
let czi = z.get_child_index(cc);
assert_eq!(czi, i);
for k in 0..OctVec::<u8>::MAX_CHILDREN {
let ccc = cc.get_child(k);
assert_eq!(z.get_child_index(ccc), i);
assert_eq!(c.get_child_index(ccc), j);
assert_eq!(cc.get_child_index(ccc), k);
}
}
}
}
#[test]
fn can_subdivide() {
let z: QuadVec = QuadVec::root();
let c1 = z.get_child(0);
let c12 = c1.get_child(0);
let tgt = QuadVec::build(0, 0, 2);
println!("{tgt:?}, {c12:?}, {}", tgt.can_subdivide(c12, 3));
println!("{tgt:?}, {c1:?}, {}", tgt.can_subdivide(c1, 3));
}
}