all_is_cubes_base/math/
axis.rs

1use core::fmt;
2
3use crate::math::{Face6, Rgb};
4
5/// Enumeration of the axes of three-dimensional space.
6///
7/// Can be used to infallibly index 3-component arrays and vectors.
8///
9/// See also:
10///
11/// * [`Face6`] specifies an axis and a direction on the axis.
12#[expect(clippy::exhaustive_enums)]
13#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, exhaust::Exhaust)]
14#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
15// do after tests:#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16#[repr(u8)]
17#[allow(missing_docs)]
18pub enum Axis {
19    X = 0,
20    Y = 1,
21    Z = 2,
22}
23
24impl Axis {
25    /// All three axes in the standard order, [X, Y, Z].
26    pub const ALL: [Self; 3] = [Self::X, Self::Y, Self::Z];
27
28    /// Returns a standard color to denote this axis among the three axes.
29    /// All colors have equal luminance.
30    ///
31    /// * X = red
32    /// * Y = green
33    /// * Z = blue
34    #[mutants::skip]
35    #[inline]
36    pub fn color(&self) -> Rgb {
37        match self {
38            Axis::X => Rgb::UNIFORM_LUMINANCE_RED,
39            Axis::Y => Rgb::UNIFORM_LUMINANCE_GREEN,
40            Axis::Z => Rgb::UNIFORM_LUMINANCE_BLUE,
41        }
42    }
43
44    /// Returns the [`Face6`] value which corresponds to the positive direction on this axis.
45    #[inline]
46    pub fn positive_face(&self) -> Face6 {
47        match self {
48            Axis::X => Face6::PX,
49            Axis::Y => Face6::PY,
50            Axis::Z => Face6::PZ,
51        }
52    }
53
54    /// Returns the [`Face6`] value which corresponds to the negative direction on this axis.
55    #[inline]
56    pub fn negative_face(&self) -> Face6 {
57        match self {
58            Axis::X => Face6::NX,
59            Axis::Y => Face6::NY,
60            Axis::Z => Face6::NZ,
61        }
62    }
63
64    /// Convert the axis to a number for indexing 3-element arrays.
65    #[inline]
66    pub const fn index(self) -> usize {
67        self as usize
68    }
69
70    /// Maps X to Y, Y to Z, and Z to X.
71    #[inline]
72    #[must_use]
73    pub const fn increment(self) -> Self {
74        match self {
75            Axis::X => Axis::Y,
76            Axis::Y => Axis::Z,
77            Axis::Z => Axis::X,
78        }
79    }
80
81    /// Maps X to Z, Y to X, and Z to Y.
82    #[inline]
83    #[must_use]
84    pub const fn decrement(self) -> Self {
85        match self {
86            Axis::X => Axis::Z,
87            Axis::Y => Axis::X,
88            Axis::Z => Axis::Y,
89        }
90    }
91}
92
93/// Format the axis as one of the strings "x", "y", or "z" (lowercase).
94impl fmt::LowerHex for Axis {
95    #[allow(clippy::missing_inline_in_public_items)]
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        f.write_str(match self {
98            Axis::X => "x",
99            Axis::Y => "y",
100            Axis::Z => "z",
101        })
102    }
103}
104/// Format the axis as one of the strings "X", "Y", or "Z" (uppercase).
105impl fmt::UpperHex for Axis {
106    #[allow(clippy::missing_inline_in_public_items)]
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        f.write_str(match self {
109            Axis::X => "X",
110            Axis::Y => "Y",
111            Axis::Z => "Z",
112        })
113    }
114}
115
116impl From<Axis> for u8 {
117    #[inline]
118    fn from(value: Axis) -> Self {
119        value as u8
120    }
121}
122impl From<Axis> for usize {
123    #[inline]
124    fn from(value: Axis) -> Self {
125        value as usize
126    }
127}
128
129mod impl_index_axis {
130    use super::Axis;
131    use core::ops;
132
133    impl<T> ops::Index<Axis> for [T; 3] {
134        type Output = T;
135
136        #[inline]
137        fn index(&self, index: Axis) -> &Self::Output {
138            &self[index as usize]
139        }
140    }
141    impl<T> ops::IndexMut<Axis> for [T; 3] {
142        #[inline]
143        fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
144            &mut self[index as usize]
145        }
146    }
147
148    macro_rules! impl_xyz_e {
149        ($x:ident $y:ident $z:ident, $($type:tt)*) => {
150            impl<T, U> ops::Index<Axis> for $($type)*<T, U> {
151                type Output = T;
152
153                #[inline]
154                fn index(&self, index: Axis) -> &Self::Output {
155                    match index {
156                        Axis::X => &self.$x,
157                        Axis::Y => &self.$y,
158                        Axis::Z => &self.$z,
159                    }
160                }
161            }
162            impl<T, U> ops::IndexMut<Axis> for $($type)*<T, U> {
163                #[inline]
164                fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
165                    match index {
166                        Axis::X => &mut self.$x,
167                        Axis::Y => &mut self.$y,
168                        Axis::Z => &mut self.$z,
169                    }
170                }
171            }
172        };
173    }
174    impl_xyz_e!(x y z, euclid::Vector3D);
175    impl_xyz_e!(x y z, euclid::Point3D);
176    impl_xyz_e!(width height depth, euclid::Size3D);
177
178    // `Cube` also has implementations like this, in its own module.
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn axis_conversion() {
187        assert_eq!(u8::from(Axis::X), 0);
188        assert_eq!(u8::from(Axis::Y), 1);
189        assert_eq!(u8::from(Axis::Z), 2);
190
191        for axis in Axis::ALL {
192            assert_eq!(usize::from(axis), usize::from(u8::from(axis)));
193            assert_eq!(usize::from(axis), axis.index());
194        }
195    }
196
197    #[test]
198    fn axis_fmt() {
199        use Axis::*;
200        assert_eq!(
201            format!("{X:x} {Y:x} {Z:x} {X:X} {Y:X} {Z:X}"),
202            "x y z X Y Z"
203        );
204    }
205
206    #[test]
207    fn inc_dec_properties() {
208        for axis in Axis::ALL {
209            assert_ne!(axis, axis.increment());
210            assert_ne!(axis, axis.decrement());
211            assert_eq!(axis, axis.increment().decrement());
212            assert_eq!(axis, axis.decrement().increment());
213        }
214    }
215}