use crate::hex::{Hex, HexAx, HexCb};
use std::isize;
use std::ops::BitAnd;
use std::{cmp::max, cmp::min, collections::HashSet};
pub struct HexRange {
x_range: (isize, isize),
y_range: (isize, isize),
z_range: (isize, isize),
}
impl HexRange {
pub fn new(x_range: (isize, isize), y_range: (isize, isize), z_range: (isize, isize)) -> Self {
HexRange {
x_range,
y_range,
z_range,
}
}
pub fn sphere<T>(center: &T, r: isize) -> HexRange
where
T: Hex,
{
let center_cb: HexCb = (*center).into();
HexRange {
x_range: (center_cb.q - r, center_cb.q + r),
y_range: (center_cb.r - r, center_cb.r + r),
z_range: (center_cb.s - r, center_cb.s + r),
}
}
pub fn compute<T>(&self) -> HashSet<T>
where
T: Hex,
{
let mut in_range: HashSet<T> = HashSet::with_capacity(hex_sphere_size(
[
self.x_range.1 - self.x_range.0,
self.y_range.1 - self.y_range.0,
self.z_range.1 - self.z_range.0,
]
.into_iter()
.min()
.unwrap()
.clamp(0, isize::MAX),
) as usize);
for x in self.x_range.0..=self.x_range.1 {
for y in max(self.y_range.0, -x - self.z_range.1)..=min(self.y_range.1, -x - self.z_range.0) {
in_range.insert(HexAx::new(x, y).into());
}
}
in_range
}
}
impl BitAnd for HexRange {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
HexRange::new(
(
max(self.x_range.0, rhs.x_range.0),
min(self.x_range.1, rhs.x_range.1),
),
(
max(self.y_range.0, rhs.y_range.0),
min(self.y_range.1, rhs.y_range.1),
),
(
max(self.z_range.0, rhs.z_range.0),
min(self.z_range.1, rhs.z_range.1),
),
)
}
}
pub fn hex_sphere_size(r: isize) -> isize {
assert!(r >= 0, "hex sphere radius cannot be larger than 0");
(1 + 6 * r as usize * (r as usize + 1) / 2) as isize
}
#[cfg(test)]
mod test {
use super::*;
use crate::hex::{HexAx, HexCb};
#[test]
fn sphere_case_0() {
let center = HexCb::new(0, 0, 0);
let radius = 3;
let range = HexRange::sphere(¢er, radius).compute();
verify_sphere(range, ¢er, radius);
}
#[test]
fn sphere_case_1() {
let center = HexAx::new(2, 1);
let radius = 5;
let range = HexRange::sphere(¢er, radius).compute();
verify_sphere(range, ¢er, radius);
}
fn verify_sphere<T>(range: HashSet<T>, center: &T, radius: isize)
where
T: Hex,
{
assert_eq!(hex_sphere_size(radius), range.len() as isize);
assert!(range.contains(center));
for x in range {
assert!(x.dist(center) <= radius);
}
}
#[test]
fn intersect_case_0() {
let sphere0 = HexRange::sphere(&HexCb::new(-4, 0, 4), 3);
let sphere1 = HexRange::sphere(&HexCb::new(1, 1, -2), 3);
let range = (sphere0 & sphere1).compute::<HexCb>();
assert_eq!(range.len(), 2);
assert!(range.contains(&HexCb::new(-1, 0, 1)));
assert!(range.contains(&HexCb::new(-2, 1, 1)));
}
#[test]
fn intersect_case_1() {
let sphere0 = HexRange::sphere(&HexCb::new(-4, 0, 4), 3);
let sphere1 = HexRange::sphere(&HexCb::new(4, 0, -4), 3);
let range = (sphere0 & sphere1).compute::<HexCb>();
assert!(range.is_empty());
}
}