1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
use super::CoordIJK;
use crate::math::{abs, round};

/// Cube coordinates.
///
/// Cube coordinates are more suitable than `IJK` for linear interpolation.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct CoordCube {
    /// `i` component.
    pub i: i32,
    /// `j` component.
    pub j: i32,
    /// `k` component.
    pub k: i32,
}

impl CoordCube {
    /// Initializes a new cube coordinate with the specified component values.
    pub const fn new(i: i32, j: i32, k: i32) -> Self {
        Self { i, j, k }
    }

    /// Translate the coordinates by the specified offset.
    ///
    /// Algorithm from <https://www.redblobgames.com/grids/hexagons/#rounding/>
    pub fn translate(&self, offsets: (f64, f64, f64)) -> Self {
        let i = f64::from(self.i) + offsets.0;
        let j = f64::from(self.j) + offsets.1;
        let k = f64::from(self.k) + offsets.2;

        #[allow(clippy::cast_possible_truncation)] // on purpose
        let (mut ri, mut rj, mut rk) =
            { (round(i) as i32, round(j) as i32, round(k) as i32) };

        let i_diff = abs(f64::from(ri) - i);
        let j_diff = abs(f64::from(rj) - j);
        let k_diff = abs(f64::from(rk) - k);

        // Round, maintaining valid cube coords.
        if i_diff > j_diff && i_diff > k_diff {
            ri = -rj - rk;
        } else if j_diff > k_diff {
            rj = -ri - rk;
        } else {
            rk = -ri - rj;
        }

        Self::new(ri, rj, rk)
    }
}

impl From<CoordCube> for CoordIJK {
    fn from(value: CoordCube) -> Self {
        Self::new(-value.i, value.j, 0).normalize()
    }
}