use crate::constants::{EPSILON, M_ONESEVENTH, M_ONETHIRD, M_RSIN60, M_SQRT3_2, M_SQRT7};
use crate::math::extensions::_ipow; use crate::types::{CoordIJ, CoordIJK, Direction};
use crate::Vec2d;
use std::f64; use std::i32;
#[rustfmt::skip] pub(crate) static UNIT_VECS: [CoordIJK; 7] = [
CoordIJK { i: 0, j: 0, k: 0 }, CoordIJK { i: 0, j: 0, k: 1 }, CoordIJK { i: 0, j: 1, k: 0 }, CoordIJK { i: 0, j: 1, k: 1 }, CoordIJK { i: 1, j: 0, k: 0 }, CoordIJK { i: 1, j: 0, k: 1 }, CoordIJK { i: 1, j: 1, k: 0 }, ];
#[inline]
pub(crate) fn _set_ijk(ijk: &mut CoordIJK, i: i32, j: i32, k: i32) {
ijk.i = i;
ijk.j = j;
ijk.k = k;
}
#[inline]
#[must_use]
pub(crate) fn _ijk_matches(c1: &CoordIJK, c2: &CoordIJK) -> bool {
c1.i == c2.i && c1.j == c2.j && c1.k == c2.k
}
#[inline]
pub(crate) fn _ijk_add(h1: &CoordIJK, h2: &CoordIJK, sum: &mut CoordIJK) {
sum.i = h1.i.saturating_add(h2.i);
sum.j = h1.j.saturating_add(h2.j);
sum.k = h1.k.saturating_add(h2.k);
}
#[inline]
pub(crate) fn _ijk_sub(h1: &CoordIJK, h2: &CoordIJK, diff: &mut CoordIJK) {
diff.i = h1.i.saturating_sub(h2.i);
diff.j = h1.j.saturating_sub(h2.j);
diff.k = h1.k.saturating_sub(h2.k);
}
#[inline]
pub(crate) fn _ijk_scale(c: &mut CoordIJK, factor: i32) {
c.i = c.i.saturating_mul(factor);
c.j = c.j.saturating_mul(factor);
c.k = c.k.saturating_mul(factor);
}
#[inline]
fn _add_i32s_overflows(a: i32, b: i32) -> bool {
a.checked_add(b).is_none()
}
#[inline]
fn _sub_i32s_overflows(a: i32, b: i32) -> bool {
a.checked_sub(b).is_none()
}
#[inline]
#[must_use]
pub(crate) fn _ijk_normalize_could_overflow(ijk: &CoordIJK) -> bool {
if ijk.i < 0 && _add_i32s_overflows(ijk.j, -ijk.i) {
return true;
}
if ijk.i < 0 && _add_i32s_overflows(0, -ijk.i) {
return true;
}
if ijk.j < 0 && _add_i32s_overflows(ijk.i, -ijk.j) {
return true;
}
if ijk.j < 0 && _add_i32s_overflows(0, -ijk.j) {
return true;
}
let (max_val, min_val) = if ijk.i > ijk.j { (ijk.i, ijk.j) } else { (ijk.j, ijk.i) };
if min_val < 0 {
if _add_i32s_overflows(max_val, min_val) {
return true;
}
if _sub_i32s_overflows(0, min_val) {
return true;
} if _sub_i32s_overflows(max_val, min_val) {
return true;
}
}
false
}
#[inline]
pub fn _ijk_normalize(c: &mut CoordIJK) {
if c.i < 0 {
c.j = c.j.saturating_sub(c.i);
c.k = c.k.saturating_sub(c.i);
c.i = 0;
}
if c.j < 0 {
c.i = c.i.saturating_sub(c.j);
c.k = c.k.saturating_sub(c.j);
c.j = 0;
}
if c.k < 0 {
c.i = c.i.saturating_sub(c.k);
c.j = c.j.saturating_sub(c.k);
c.k = 0;
}
let min_val = c.i.min(c.j).min(c.k);
if min_val > 0 {
c.i -= min_val;
c.j -= min_val;
c.k -= min_val;
}
}
#[inline]
#[must_use]
pub(crate) fn _unit_ijk_to_digit(ijk: &CoordIJK) -> Direction {
let mut c = *ijk; _ijk_normalize(&mut c);
if _ijk_matches(&c, &UNIT_VECS[0]) {
Direction::Center
} else if _ijk_matches(&c, &UNIT_VECS[1]) {
Direction::KAxes
} else if _ijk_matches(&c, &UNIT_VECS[2]) {
Direction::JAxes
} else if _ijk_matches(&c, &UNIT_VECS[3]) {
Direction::JkAxes
} else if _ijk_matches(&c, &UNIT_VECS[4]) {
Direction::IAxes
} else if _ijk_matches(&c, &UNIT_VECS[5]) {
Direction::IkAxes
} else if _ijk_matches(&c, &UNIT_VECS[6]) {
Direction::IjAxes
} else {
Direction::InvalidDigit
}
}
#[inline]
pub(crate) fn _neighbor(ijk: &mut CoordIJK, digit: Direction) {
if digit != Direction::Center && digit != Direction::InvalidDigit {
let temp_h2 = UNIT_VECS[digit as usize]; let mut result = CoordIJK::default(); _ijk_add(ijk, &temp_h2, &mut result); *ijk = result; _ijk_normalize(ijk);
}
}
#[inline]
#[inline]
pub(crate) fn _hex2d_to_coord_ijk(v: &Vec2d, h: &mut CoordIJK) {
h.k = 0;
let a1 = v.x.abs();
let a2 = v.y.abs();
let x2 = a2 * M_RSIN60;
let x1 = a1 + x2 / 2.0;
let m1 = (x1 + EPSILON) as i32;
let m2 = (x2 + EPSILON) as i32;
let r1 = x1 - m1 as f64;
let r2 = x2 - m2 as f64;
const ONE_THIRD: f64 = 1.0 / 3.0; const TWO_THIRDS: f64 = 2.0 / 3.0;
if r1 < 0.5 {
if r1 < ONE_THIRD {
if r2 < (1.0 + r1) / 2.0 {
h.i = m1;
h.j = m2;
} else {
h.i = m1;
h.j = m2 + 1;
}
} else {
if r2 < (1.0 - r1) {
h.j = m2;
} else {
h.j = m2 + 1;
}
if ((1.0 - r1) <= r2) && (r2 < (2.0 * r1)) {
h.i = m1 + 1;
} else {
h.i = m1;
}
}
} else {
if r1 < TWO_THIRDS {
if r2 < (2.0 * r1 - 1.0) {
h.j = m2 + 1;
} else {
h.j = m2;
}
if ((2.0 * r1 - 1.0) < r2) && (r2 < (1.0 - r1)) {
h.i = m1;
} else {
h.i = m1 + 1;
}
} else {
h.i = m1 + 1;
if r2 < (r1 / 2.0) {
h.j = m2;
} else {
h.j = m2 + 1;
}
}
}
if v.x < 0.0 {
if (h.j % 2) == 0 {
let axisi = h.j / 2;
let diff = h.i - axisi;
h.i -= 2 * diff; } else {
let axisi = (h.j + 1) / 2;
let diff = h.i - axisi;
h.i -= 2 * diff + 1; }
}
if v.y < 0.0 {
let term = 2 * h.j + 1;
h.i -= term / 2;
h.j = -h.j;
}
_ijk_normalize(h);
}
#[inline]
pub(crate) fn _ijk_to_hex2d(h: &CoordIJK, v: &mut Vec2d) {
let i = h.i - h.k;
let j = h.j - h.k;
v.x = i as f64 - 0.5 * j as f64;
v.y = j as f64 * M_SQRT3_2;
}
fn h3_lround(val: f64) -> i32 {
if val > 0.0 {
(val + 0.5).floor() as i32
} else {
(val - 0.5).ceil() as i32
}
}
fn lround_c99_style(val: f64) -> i32 {
if val > 0.0 {
(val + 0.5).floor() as i32
} else if val < 0.0 {
(val - 0.5).ceil() as i32
} else {
0
}
}
pub(crate) fn _up_ap7_checked(ijk: &mut CoordIJK) -> Result<(), crate::types::H3Error> {
let i_ax = ijk.i - ijk.k;
let j_ax = ijk.j - ijk.k;
let term1_i = i_ax.checked_mul(3).ok_or(crate::types::H3Error::Failed)?;
let new_i_num = term1_i.checked_sub(j_ax).ok_or(crate::types::H3Error::Failed)?;
let term1_j = j_ax.checked_mul(2).ok_or(crate::types::H3Error::Failed)?;
let new_j_num = i_ax.checked_add(term1_j).ok_or(crate::types::H3Error::Failed)?;
ijk.i = lround_c99_style(new_i_num as f64 * M_ONESEVENTH);
ijk.j = lround_c99_style(new_j_num as f64 * M_ONESEVENTH);
ijk.k = 0;
if _ijk_normalize_could_overflow(ijk) { }
_ijk_normalize(ijk);
Ok(())
}
pub(crate) fn _up_ap7r_checked(ijk: &mut CoordIJK) -> Result<(), crate::types::H3Error> {
let i_ax = ijk.i - ijk.k;
let j_ax = ijk.j - ijk.k;
let term1_i = i_ax.checked_mul(2).ok_or(crate::types::H3Error::Failed)?;
let new_i_num = term1_i.checked_add(j_ax).ok_or(crate::types::H3Error::Failed)?;
let term1_j = j_ax.checked_mul(3).ok_or(crate::types::H3Error::Failed)?;
let new_j_num = term1_j.checked_sub(i_ax).ok_or(crate::types::H3Error::Failed)?;
ijk.i = lround_c99_style(new_i_num as f64 * M_ONESEVENTH);
ijk.j = lround_c99_style(new_j_num as f64 * M_ONESEVENTH);
ijk.k = 0;
_ijk_normalize(ijk);
Ok(())
}
#[inline]
pub(crate) fn _up_ap7(ijk: &mut CoordIJK) {
let i = ijk.i - ijk.k;
let j = ijk.j - ijk.k;
ijk.i = lround_c99_style((3 * i - j) as f64 * M_ONESEVENTH);
ijk.j = lround_c99_style((i + 2 * j) as f64 * M_ONESEVENTH);
ijk.k = 0;
_ijk_normalize(ijk);
}
#[inline]
pub(crate) fn _up_ap7r(ijk: &mut CoordIJK) {
let i = ijk.i - ijk.k;
let j = ijk.j - ijk.k;
ijk.i = lround_c99_style((2 * i + j) as f64 * M_ONESEVENTH);
ijk.j = lround_c99_style((3 * j - i) as f64 * M_ONESEVENTH);
ijk.k = 0;
_ijk_normalize(ijk);
}
#[inline]
pub(crate) fn _down_ap7(ijk: &mut CoordIJK) {
let i_vec = CoordIJK { i: 3, j: 0, k: 1 };
let j_vec = CoordIJK { i: 1, j: 3, k: 0 };
let k_vec = CoordIJK { i: 0, j: 1, k: 3 };
let mut temp_i = i_vec;
_ijk_scale(&mut temp_i, ijk.i);
let mut temp_j = j_vec;
_ijk_scale(&mut temp_j, ijk.j);
let mut temp_k = k_vec;
_ijk_scale(&mut temp_k, ijk.k);
let mut sum1 = CoordIJK::default();
_ijk_add(&temp_i, &temp_j, &mut sum1);
_ijk_add(&sum1, &temp_k, ijk);
_ijk_normalize(ijk);
}
#[inline]
pub(crate) fn _down_ap7r(ijk: &mut CoordIJK) {
let i_vec = CoordIJK { i: 3, j: 1, k: 0 };
let j_vec = CoordIJK { i: 0, j: 3, k: 1 };
let k_vec = CoordIJK { i: 1, j: 0, k: 3 };
let mut temp_i = i_vec;
_ijk_scale(&mut temp_i, ijk.i);
let mut temp_j = j_vec;
_ijk_scale(&mut temp_j, ijk.j);
let mut temp_k = k_vec;
_ijk_scale(&mut temp_k, ijk.k);
let mut sum1 = CoordIJK::default();
_ijk_add(&temp_i, &temp_j, &mut sum1);
_ijk_add(&sum1, &temp_k, ijk);
_ijk_normalize(ijk);
}
#[inline]
pub(crate) fn _down_ap3(ijk: &mut CoordIJK) {
let i_vec = CoordIJK { i: 2, j: 0, k: 1 };
let j_vec = CoordIJK { i: 1, j: 2, k: 0 };
let k_vec = CoordIJK { i: 0, j: 1, k: 2 };
let mut temp_i = i_vec;
_ijk_scale(&mut temp_i, ijk.i);
let mut temp_j = j_vec;
_ijk_scale(&mut temp_j, ijk.j);
let mut temp_k = k_vec;
_ijk_scale(&mut temp_k, ijk.k);
let mut sum1 = CoordIJK::default();
_ijk_add(&temp_i, &temp_j, &mut sum1);
_ijk_add(&sum1, &temp_k, ijk);
_ijk_normalize(ijk);
}
#[inline]
pub(crate) fn _down_ap3r(ijk: &mut CoordIJK) {
let i_vec = CoordIJK { i: 2, j: 1, k: 0 };
let j_vec = CoordIJK { i: 0, j: 2, k: 1 };
let k_vec = CoordIJK { i: 1, j: 0, k: 2 };
let mut temp_i = i_vec;
_ijk_scale(&mut temp_i, ijk.i);
let mut temp_j = j_vec;
_ijk_scale(&mut temp_j, ijk.j);
let mut temp_k = k_vec;
_ijk_scale(&mut temp_k, ijk.k);
let mut sum1 = CoordIJK::default();
_ijk_add(&temp_i, &temp_j, &mut sum1);
_ijk_add(&sum1, &temp_k, ijk);
_ijk_normalize(ijk);
}
#[inline]
pub(crate) fn _ijk_rotate60_ccw(ijk: &mut CoordIJK) {
let i_vec = CoordIJK { i: 1, j: 1, k: 0 };
let j_vec = CoordIJK { i: 0, j: 1, k: 1 };
let k_vec = CoordIJK { i: 1, j: 0, k: 1 };
let mut temp_i = i_vec;
_ijk_scale(&mut temp_i, ijk.i);
let mut temp_j = j_vec;
_ijk_scale(&mut temp_j, ijk.j);
let mut temp_k = k_vec;
_ijk_scale(&mut temp_k, ijk.k);
let mut sum1 = CoordIJK::default();
_ijk_add(&temp_i, &temp_j, &mut sum1);
_ijk_add(&sum1, &temp_k, ijk);
_ijk_normalize(ijk);
}
#[inline]
pub(crate) fn _ijk_rotate60_cw(ijk: &mut CoordIJK) {
let i_vec = CoordIJK { i: 1, j: 0, k: 1 };
let j_vec = CoordIJK { i: 1, j: 1, k: 0 };
let k_vec = CoordIJK { i: 0, j: 1, k: 1 };
let mut temp_i = i_vec;
_ijk_scale(&mut temp_i, ijk.i);
let mut temp_j = j_vec;
_ijk_scale(&mut temp_j, ijk.j);
let mut temp_k = k_vec;
_ijk_scale(&mut temp_k, ijk.k);
let mut sum1 = CoordIJK::default();
_ijk_add(&temp_i, &temp_j, &mut sum1);
_ijk_add(&sum1, &temp_k, ijk);
_ijk_normalize(ijk);
}
#[inline]
#[must_use]
pub(crate) fn _rotate60_ccw(digit: Direction) -> Direction {
use Direction::*;
match digit {
KAxes => IkAxes,
IkAxes => IAxes,
IAxes => IjAxes,
IjAxes => JAxes,
JAxes => JkAxes,
JkAxes => KAxes,
_ => digit, }
}
#[inline]
#[must_use]
pub(crate) fn _rotate60_cw(digit: Direction) -> Direction {
use Direction::*;
match digit {
KAxes => JkAxes,
JkAxes => JAxes,
JAxes => IjAxes,
IjAxes => IAxes,
IAxes => IkAxes,
IkAxes => KAxes,
_ => digit, }
}
#[inline]
#[must_use]
pub(crate) fn ijk_distance(c1: &CoordIJK, c2: &CoordIJK) -> i32 {
let mut diff = CoordIJK::default();
_ijk_sub(c1, c2, &mut diff);
_ijk_normalize(&mut diff);
diff.i.abs().max(diff.j.abs()).max(diff.k.abs())
}
#[inline]
pub(crate) fn ijk_to_ij(ijk: &CoordIJK, ij: &mut CoordIJ) {
ij.i = ijk.i - ijk.k;
ij.j = ijk.j - ijk.k;
}
pub(crate) fn ij_to_ijk(ij: &CoordIJ, ijk: &mut CoordIJK) -> Result<(), crate::types::H3Error> {
ijk.i = ij.i;
ijk.j = ij.j;
ijk.k = 0;
if _ijk_normalize_could_overflow(ijk) {
return Err(crate::types::H3Error::Failed); }
_ijk_normalize(ijk);
Ok(())
}
pub fn ijk_to_cube(ijk: &mut CoordIJK) {
let ia = ijk.i - ijk.k;
let ja = ijk.j - ijk.k;
let ka = -ia - ja;
ijk.i = ia;
ijk.j = ja;
ijk.k = ka;
}
pub fn cube_to_ijk(ijk: &mut CoordIJK) {
ijk.i = ijk.i.saturating_neg();
ijk.k = 0;
_ijk_normalize(ijk);
}
#[cfg(test)]
mod tests {
use crate::Vec2d;
use super::*;
#[test]
fn test_set_ijk() {
let mut ijk = CoordIJK::default();
_set_ijk(&mut ijk, 1, 2, 3);
assert_eq!(ijk, CoordIJK { i: 1, j: 2, k: 3 });
}
#[test]
fn test_ijk_matches() {
let c1 = CoordIJK { i: 1, j: 2, k: 3 };
let c2 = CoordIJK { i: 1, j: 2, k: 3 };
let c3 = CoordIJK { i: 4, j: 2, k: 3 };
assert!(_ijk_matches(&c1, &c2));
assert!(!_ijk_matches(&c1, &c3));
}
#[test]
fn test_ijk_add() {
let h1 = CoordIJK { i: 1, j: 2, k: -3 };
let h2 = CoordIJK { i: 4, j: -5, k: 6 };
let mut sum = CoordIJK::default();
_ijk_add(&h1, &h2, &mut sum);
assert_eq!(sum, CoordIJK { i: 5, j: -3, k: 3 });
let h_max = CoordIJK {
i: i32::MAX,
j: 0,
k: 0,
};
let h_one = CoordIJK { i: 1, j: 0, k: 0 };
_ijk_add(&h_max, &h_one, &mut sum);
assert_eq!(
sum,
CoordIJK {
i: i32::MAX,
j: 0,
k: 0
}
);
}
#[test]
fn test_ijk_sub() {
let h1 = CoordIJK { i: 1, j: 2, k: -3 };
let h2 = CoordIJK { i: 4, j: -5, k: 6 };
let mut diff = CoordIJK::default();
_ijk_sub(&h1, &h2, &mut diff);
assert_eq!(diff, CoordIJK { i: -3, j: 7, k: -9 });
let h_min = CoordIJK {
i: i32::MIN,
j: 0,
k: 0,
};
let h_one = CoordIJK { i: 1, j: 0, k: 0 };
_ijk_sub(&h_min, &h_one, &mut diff);
assert_eq!(
diff,
CoordIJK {
i: i32::MIN,
j: 0,
k: 0
}
);
}
#[test]
fn test_ijk_scale() {
let mut c = CoordIJK { i: 1, j: -2, k: 3 };
_ijk_scale(&mut c, 2);
assert_eq!(c, CoordIJK { i: 2, j: -4, k: 6 });
_ijk_scale(&mut c, -1);
assert_eq!(c, CoordIJK { i: -2, j: 4, k: -6 });
_ijk_scale(&mut c, 0);
assert_eq!(c, CoordIJK { i: 0, j: 0, k: 0 });
let mut c_max = CoordIJK {
i: i32::MAX / 2 + 1,
j: 0,
k: 0,
};
_ijk_scale(&mut c_max, 2);
assert_eq!(
c_max,
CoordIJK {
i: i32::MAX,
j: 0,
k: 0
}
);
}
#[test]
fn test_ijk_normalize() {
let mut c = CoordIJK { i: 0, j: 0, k: 0 };
_ijk_normalize(&mut c);
assert_eq!(c, CoordIJK { i: 0, j: 0, k: 0 }, "0,0,0 normalizes to 0,0,0");
_set_ijk(&mut c, 2, 3, 4); _ijk_normalize(&mut c);
assert_eq!(c, CoordIJK { i: 0, j: 1, k: 2 }, "positive components");
_set_ijk(&mut c, -2, -3, -4); _ijk_normalize(&mut c);
assert_eq!(c, CoordIJK { i: 2, j: 1, k: 0 }, "negative components");
_set_ijk(&mut c, 2, -1, 0); _ijk_normalize(&mut c);
assert_eq!(c, CoordIJK { i: 3, j: 0, k: 1 }, "mixed components (1)");
_set_ijk(&mut c, 10, 20, 5); _ijk_normalize(&mut c);
assert_eq!(c, CoordIJK { i: 5, j: 15, k: 0 }, "remove min value");
}
#[test]
fn test_ijk_normalize_saturating_behavior() {
let mut c = CoordIJK {
i: i32::MIN,
j: 1,
k: 1,
};
_ijk_normalize(&mut c); assert_eq!(
c,
CoordIJK {
i: 0,
j: i32::MAX,
k: i32::MAX
}
);
c = CoordIJK {
i: 1,
j: i32::MIN,
k: 1,
};
_ijk_normalize(&mut c); assert_eq!(
c,
CoordIJK {
i: i32::MAX,
j: 0,
k: i32::MAX
}
);
c = CoordIJK {
i: 1,
j: 1,
k: i32::MIN,
};
_ijk_normalize(&mut c); assert_eq!(
c,
CoordIJK {
i: i32::MAX,
j: i32::MAX,
k: 0
}
);
c = CoordIJK {
i: -10,
j: i32::MIN, k: -20,
};
_ijk_normalize(&mut c);
assert_eq!(
c,
CoordIJK {
i: 2147483638, j: 0,
k: 2147483628 },
"Normalization of {{i:-10, j:MIN, k:-20}} failed" );
}
#[inline]
#[must_use]
pub(crate) fn _ijk_normalize_could_overflow(ijk: &CoordIJK) -> bool {
let neg_i = ijk.i.checked_neg();
let neg_j = ijk.j.checked_neg();
let neg_k = ijk.k.checked_neg();
if ijk.i < 0 {
if neg_i.is_none() {
return true;
} if _add_i32s_overflows(ijk.j, neg_i.unwrap()) {
return true;
}
if _add_i32s_overflows(ijk.k, neg_i.unwrap()) {
return true;
} }
if ijk.j < 0 {
if neg_j.is_none() {
return true;
} if _add_i32s_overflows(ijk.i, neg_j.unwrap()) {
return true;
}
if _add_i32s_overflows(ijk.k, neg_j.unwrap()) {
return true;
}
}
if ijk.k < 0 {
if neg_k.is_none() {
return true;
} if _add_i32s_overflows(ijk.i, neg_k.unwrap()) {
return true;
}
if _add_i32s_overflows(ijk.j, neg_k.unwrap()) {
return true;
}
}
if ijk.k == 0 {
let (max_c, min_c) = if ijk.i > ijk.j { (ijk.i, ijk.j) } else { (ijk.j, ijk.i) };
if min_c < 0 {
if _add_i32s_overflows(max_c, min_c) {
return true;
}
if min_c == i32::MIN && 0_i32.checked_sub(min_c).is_none() {
return true;
}
else if min_c != i32::MIN && _sub_i32s_overflows(0, min_c) {
return true;
}
if _sub_i32s_overflows(max_c, min_c) {
return true;
}
}
}
false
}
#[test]
fn test_unit_ijk_to_digit() {
assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 0, j: 0, k: 0 }),
Direction::Center,
"Center"
);
assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 0, j: 0, k: 1 }),
Direction::KAxes,
"K"
);
assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 0, j: 1, k: 0 }),
Direction::JAxes,
"J"
);
assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 0, j: 1, k: 1 }),
Direction::JkAxes,
"JK"
);
assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 1, j: 0, k: 0 }),
Direction::IAxes,
"I"
);
assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 1, j: 0, k: 1 }),
Direction::IkAxes,
"IK"
);
assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 1, j: 1, k: 0 }),
Direction::IjAxes,
"IJ"
);
assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 2, j: 2, k: 2 }),
Direction::Center,
"Unnormalized Center"
); assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 1, j: 1, k: 2 }),
Direction::KAxes,
"Unnormalized K"
);
assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 2, j: 0, k: 0 }),
Direction::InvalidDigit,
"Not a unit vector (I=2)"
);
assert_eq!(
_unit_ijk_to_digit(&CoordIJK { i: 1, j: 2, k: 3 }),
Direction::InvalidDigit,
"Not a unit vector (complex)"
);
}
#[test]
fn test_neighbor() {
let mut ijk = CoordIJK { i: 0, j: 0, k: 0 };
let initial_ijk = ijk;
_neighbor(&mut ijk, Direction::Center);
assert!(_ijk_matches(&ijk, &initial_ijk), "Center neighbor is self");
_neighbor(&mut ijk, Direction::IAxes);
assert!(
_ijk_matches(&ijk, &UNIT_VECS[Direction::IAxes as usize]),
"I neighbor as expected"
);
ijk = initial_ijk;
_neighbor(&mut ijk, Direction::InvalidDigit); assert!(_ijk_matches(&ijk, &initial_ijk), "Invalid neighbor is self");
_set_ijk(&mut ijk, 1, 1, 1); _ijk_normalize(&mut ijk);
_neighbor(&mut ijk, Direction::JAxes);
assert!(
_ijk_matches(&ijk, &UNIT_VECS[Direction::JAxes as usize]),
"J neighbor from normalized (1,1,1)"
);
}
fn assert_hex2d_to_coord_ijk(v: Vec2d, expected_h: CoordIJK, msg: &str) {
let mut h = CoordIJK::default();
_hex2d_to_coord_ijk(&v, &mut h);
assert!(
_ijk_matches(&h, &expected_h),
"{} - Expected ({},{},{}), Got ({},{},{})",
msg,
expected_h.i,
expected_h.j,
expected_h.k,
h.i,
h.j,
h.k
);
}
#[test]
fn test_hex2d_to_coord_ijk() {
assert_hex2d_to_coord_ijk(Vec2d { x: 0.0, y: 0.0 }, CoordIJK { i: 0, j: 0, k: 0 }, "origin");
assert_hex2d_to_coord_ijk(Vec2d { x: 1.0, y: 0.0 }, CoordIJK { i: 1, j: 0, k: 0 }, "i axis");
assert_hex2d_to_coord_ijk(Vec2d { x: -0.5, y: M_SQRT3_2 }, CoordIJK { i: 0, j: 1, k: 0 }, "j axis");
assert_hex2d_to_coord_ijk(
Vec2d { x: -0.5, y: -M_SQRT3_2 },
CoordIJK { i: 0, j: 0, k: 1 },
"k axis",
);
assert_hex2d_to_coord_ijk(Vec2d { x: 0.1, y: 0.1 }, CoordIJK { i: 0, j: 0, k: 0 }, "eps case");
assert_hex2d_to_coord_ijk(Vec2d { x: -0.1, y: -0.1 }, CoordIJK { i: 0, j: 0, k: 0 }, "eps case N");
assert_hex2d_to_coord_ijk(Vec2d { x: 0.1, y: -0.1 }, CoordIJK { i: 0, j: 0, k: 0 }, "eps case E");
assert_hex2d_to_coord_ijk(Vec2d { x: -0.1, y: 0.1 }, CoordIJK { i: 0, j: 0, k: 0 }, "eps case W");
assert_hex2d_to_coord_ijk(
Vec2d { x: 0.5, y: M_SQRT3_2 },
CoordIJK { i: 1, j: 1, k: 0 },
"IJ axis (was 'on edge' with wrong expectation)",
);
assert_hex2d_to_coord_ijk(
Vec2d { x: 0.4, y: M_SQRT3_2 },
CoordIJK { i: 1, j: 1, k: 0 }, "pert on edge1 (closer to IJ than J)",
);
assert_hex2d_to_coord_ijk(
Vec2d { x: 0.6, y: M_SQRT3_2 },
CoordIJK { i: 1, j: 1, k: 0 },
"pert on edge2",
);
assert_hex2d_to_coord_ijk(
Vec2d { x: 1.0, y: M_SQRT3_2 },
CoordIJK { i: 2, j: 2, k: 0 },
"Shared vertex",
);
}
fn assert_ijk_to_hex2d_roundtrip(h: CoordIJK, msg: &str) {
let mut v = Vec2d::default();
_ijk_to_hex2d(&h, &mut v);
let mut h2 = CoordIJK::default();
_hex2d_to_coord_ijk(&v, &mut h2);
assert!(_ijk_matches(&h, &h2), "{} - roundtrip", msg);
}
#[test]
fn test_ijk_to_hex2d_roundtrip() {
assert_ijk_to_hex2d_roundtrip(CoordIJK { i: 0, j: 0, k: 0 }, "origin");
for dir_enum_val in 1..=6 {
let dir = unsafe { std::mem::transmute::<u8, Direction>(dir_enum_val as u8) };
let unit_vec = UNIT_VECS[dir as usize];
assert_ijk_to_hex2d_roundtrip(unit_vec, &format!("unit vec {:?}", dir));
}
assert_ijk_to_hex2d_roundtrip(CoordIJK { i: 2, j: 0, k: 1 }, "arbitrary"); }
#[test]
fn test_up_ap7_checked() {
let mut ijk = CoordIJK { i: 0, j: 0, k: 0 };
assert!(_up_ap7_checked(&mut ijk).is_ok());
assert_eq!(ijk, CoordIJK { i: 0, j: 0, k: 0 });
let mut c = UNIT_VECS[Direction::KAxes as usize]; assert!(_up_ap7_checked(&mut c).is_ok());
assert_eq!(c, CoordIJK { i: 0, j: 0, k: 0 }, "K axis upAp7");
let mut c2 = UNIT_VECS[Direction::IAxes as usize]; assert!(_up_ap7_checked(&mut c2).is_ok());
assert_eq!(c2, CoordIJK { i: 0, j: 0, k: 0 }, "I axis upAp7");
let mut large_i = CoordIJK {
i: i32::MAX,
j: 0,
k: 0,
};
assert!(
_up_ap7_checked(&mut large_i).is_err(),
"Large i for up_ap7 should overflow"
);
let mut large_j = CoordIJK {
i: 0,
j: i32::MAX,
k: 0,
};
assert!(
_up_ap7_checked(&mut large_j).is_err(),
"Large j for up_ap7 should overflow"
);
}
#[test]
fn test_up_ap7r_checked() {
let mut ijk = CoordIJK { i: 0, j: 0, k: 0 };
assert!(_up_ap7r_checked(&mut ijk).is_ok());
assert_eq!(ijk, CoordIJK { i: 0, j: 0, k: 0 });
let mut c = UNIT_VECS[Direction::KAxes as usize]; assert!(_up_ap7r_checked(&mut c).is_ok());
assert_eq!(c, CoordIJK { i: 0, j: 0, k: 0 }, "K axis upAp7r");
let mut large_i = CoordIJK {
i: i32::MAX,
j: 0,
k: 0,
};
assert!(
_up_ap7r_checked(&mut large_i).is_err(),
"Large i for up_ap7r should overflow"
);
}
fn test_down_aperture(
ap_func: fn(&mut CoordIJK),
ap_r_func: fn(&mut CoordIJK),
expected_ijs: &[(i32, i32, i32)], label: &str,
) {
let mut center = CoordIJK { i: 0, j: 0, k: 0 };
ap_func(&mut center);
assert_eq!(
center,
CoordIJK {
i: expected_ijs[0].0,
j: expected_ijs[0].1,
k: expected_ijs[0].2
},
"{}: Center",
label
);
let mut center_r = CoordIJK { i: 0, j: 0, k: 0 };
ap_r_func(&mut center_r);
assert_eq!(
center_r,
CoordIJK {
i: expected_ijs[0].0,
j: expected_ijs[0].1,
k: expected_ijs[0].2
},
"{}: Center (reversed)",
label
);
let mut i_axis = UNIT_VECS[Direction::IAxes as usize];
ap_func(&mut i_axis);
assert_eq!(
i_axis,
CoordIJK {
i: expected_ijs[1].0,
j: expected_ijs[1].1,
k: expected_ijs[1].2
},
"{}: I-axis",
label
);
let mut j_axis = UNIT_VECS[Direction::JAxes as usize];
ap_func(&mut j_axis);
assert_eq!(
j_axis,
CoordIJK {
i: expected_ijs[2].0,
j: expected_ijs[2].1,
k: expected_ijs[2].2
},
"{}: J-axis",
label
);
let mut k_axis = UNIT_VECS[Direction::KAxes as usize];
ap_func(&mut k_axis);
assert_eq!(
k_axis,
CoordIJK {
i: expected_ijs[3].0,
j: expected_ijs[3].1,
k: expected_ijs[3].2
},
"{}: K-axis",
label
);
}
#[test]
fn test_down_ap7() {
let expected = [(0, 0, 0), (3, 0, 1), (1, 3, 0), (0, 1, 3)];
test_down_aperture(_down_ap7, _down_ap7r, &expected, "downAp7");
}
#[test]
fn test_down_ap3() {
let expected = [(0, 0, 0), (2, 0, 1), (1, 2, 0), (0, 1, 2)];
test_down_aperture(_down_ap3, _down_ap3r, &expected, "downAp3");
}
#[test]
fn test_ijk_rotate60() {
let mut c = CoordIJK { i: 1, j: 0, k: 0 }; _ijk_rotate60_ccw(&mut c); assert_eq!(c, CoordIJK { i: 1, j: 1, k: 0 }, "I ccw is IJ");
_ijk_rotate60_ccw(&mut c); assert_eq!(c, CoordIJK { i: 0, j: 1, k: 0 }, "IJ ccw is J");
_ijk_rotate60_ccw(&mut c); assert_eq!(c, CoordIJK { i: 0, j: 1, k: 1 }, "J ccw is JK");
_ijk_rotate60_ccw(&mut c); assert_eq!(c, CoordIJK { i: 0, j: 0, k: 1 }, "JK ccw is K");
_ijk_rotate60_ccw(&mut c); assert_eq!(c, CoordIJK { i: 1, j: 0, k: 1 }, "K ccw is IK");
_ijk_rotate60_ccw(&mut c); assert_eq!(c, CoordIJK { i: 1, j: 0, k: 0 }, "IK ccw is I");
_ijk_rotate60_cw(&mut c); assert_eq!(c, CoordIJK { i: 1, j: 0, k: 1 }, "I cw is IK");
}
#[test]
fn test_rotate_digit() {
assert_eq!(_rotate60_ccw(Direction::KAxes), Direction::IkAxes);
assert_eq!(_rotate60_cw(Direction::KAxes), Direction::JkAxes);
assert_eq!(_rotate60_ccw(Direction::Center), Direction::Center); assert_eq!(_rotate60_cw(Direction::InvalidDigit), Direction::InvalidDigit); }
#[test]
fn test_ijk_distance() {
let z = CoordIJK { i: 0, j: 0, k: 0 };
let i = CoordIJK { i: 1, j: 0, k: 0 };
let ik = CoordIJK { i: 1, j: 0, k: 1 }; let ij = CoordIJK { i: 1, j: 1, k: 0 };
let j2 = CoordIJK { i: 0, j: 2, k: 0 };
assert_eq!(ijk_distance(&z, &z), 0, "identity distance 0,0,0");
assert_eq!(ijk_distance(&i, &i), 0, "identity distance 1,0,0");
assert_eq!(ijk_distance(&ik, &ik), 0, "identity distance 1,0,1");
assert_eq!(ijk_distance(&ij, &ij), 0, "identity distance 1,1,0");
assert_eq!(ijk_distance(&j2, &j2), 0, "identity distance 0,2,0");
assert_eq!(ijk_distance(&z, &i), 1, "0,0,0 to 1,0,0");
assert_eq!(ijk_distance(&z, &j2), 2, "0,0,0 to 0,2,0");
assert_eq!(ijk_distance(&z, &ik), 1, "0,0,0 to 1,0,1"); assert_eq!(ijk_distance(&i, &ik), 1, "1,0,0 to 1,0,1"); assert_eq!(ijk_distance(&ik, &j2), 3, "1,0,1 to 0,2,0"); assert_eq!(ijk_distance(&ij, &ik), 2, "1,1,0 to 1,0,1"); }
#[test]
fn test_ijk_to_ij_and_back() {
let mut orig_ijk = CoordIJK { i: 1, j: 2, k: 0 }; _ijk_normalize(&mut orig_ijk); let mut ij = CoordIJ::default();
let mut new_ijk = CoordIJK::default();
ijk_to_ij(&orig_ijk, &mut ij);
let res = ij_to_ijk(&ij, &mut new_ijk);
assert!(res.is_ok());
assert!(
_ijk_matches(&orig_ijk, &new_ijk),
"Roundtrip IJK->IJ->IJK should match for {:?}",
orig_ijk
);
let axial_k_non_zero = CoordIJK { i: 1, j: -3, k: 2 }; let mut h3_ijk_from_axial = axial_k_non_zero;
_ijk_normalize(&mut h3_ijk_from_axial);
ijk_to_ij(&h3_ijk_from_axial, &mut ij); let res2 = ij_to_ijk(&ij, &mut new_ijk);
assert!(res2.is_ok());
assert!(
_ijk_matches(&h3_ijk_from_axial, &new_ijk),
"Roundtrip with initial non-zero k (after H3 norm) for {:?}",
h3_ijk_from_axial
);
}
#[test]
fn test_ijk_cube_transformations() {
let mut h3_ijk_normalized = CoordIJK { i: 1, j: 2, k: 0 };
let mut cube_coords = h3_ijk_normalized;
ijk_to_cube(&mut cube_coords);
let expected_cube = CoordIJK { i: 1, j: 2, k: -3 };
assert!(
_ijk_matches(&cube_coords, &expected_cube),
"ijkToCube transform failed. Expected {:?}, got {:?}",
expected_cube,
cube_coords
);
assert_eq!(
cube_coords.i + cube_coords.j + cube_coords.k,
0,
"Cube coords should sum to 0"
);
let mut roundtrip_h3_ijk = cube_coords; cube_to_ijk(&mut roundtrip_h3_ijk);
let expected_h3_ijk_after_roundtrip = CoordIJK { i: 0, j: 3, k: 1 };
assert!(
_ijk_matches(&roundtrip_h3_ijk, &expected_h3_ijk_after_roundtrip),
"cubeToIjk transform failed for specific cube. Expected {:?}, got {:?}",
expected_h3_ijk_after_roundtrip,
roundtrip_h3_ijk
);
assert!(!_ijk_matches(&h3_ijk_normalized, &roundtrip_h3_ijk));
}
}