balanced_direction/
conversions.rs

1use crate::Balance;
2
3impl Balance {
4    /// Returns a unique integer value associated with each `Balance` variant.
5    ///
6    /// This mapping assigns a unique value to each position in the 3x3 grid,
7    /// which could be useful for serialization, indexing, or logical calculations
8    /// that depend on the position.
9    ///
10    /// # Returns
11    ///
12    /// An `i8` integer representing the `Balance` variant.
13    ///
14    /// # Mapping
15    ///
16    /// - `Balance::TopLeft` => `-4`
17    /// - `Balance::Top` => `-3`
18    /// - `Balance::TopRight` => `-2`
19    /// - `Balance::Left` => `-1`
20    /// - `Balance::Center` => `0`
21    /// - `Balance::Right` => `1`
22    /// - `Balance::BottomLeft` => `2`
23    /// - `Balance::Bottom` => `3`
24    /// - `Balance::BottomRight` => `4`
25    ///
26    /// # Examples
27    ///
28    /// ```
29    /// use balanced_direction::Balance;
30    ///
31    /// let position = Balance::Center;
32    /// assert_eq!(position.to_value(), 0);
33    ///
34    /// let position = Balance::TopRight;
35    /// assert_eq!(position.to_value(), -2);
36    /// ```
37    pub fn to_value(self) -> i8 {
38        match self {
39            Balance::TopLeft => -4,
40            Balance::Top => -3,
41            Balance::TopRight => -2,
42            Balance::Left => -1,
43            Balance::Center => 0,
44            Balance::Right => 1,
45            Balance::BottomLeft => 2,
46            Balance::Bottom => 3,
47            Balance::BottomRight => 4,
48        }
49    }
50
51    /// Constructs a `Balance` variant from a given `i8` value.
52    ///
53    /// This method maps an integer value to a specific `Balance` variant.
54    /// Values outside the valid range will cause a panic.
55    ///
56    /// # Arguments
57    ///
58    /// - `value` - An `i8` integer value corresponding to a `Balance` variant.
59    ///
60    /// # Returns
61    ///
62    /// A `Balance` instance mapped from the provided integer.
63    ///
64    /// # Panics
65    ///
66    /// This function will panic if the input value is not in the range `-4..=4`.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use balanced_direction::Balance;
72    ///
73    /// let position = Balance::from_value(-4);
74    /// assert_eq!(position, Balance::TopLeft);
75    ///
76    /// let position = Balance::from_value(0);
77    /// assert_eq!(position, Balance::Center);
78    ///
79    /// // This will panic:
80    /// // let invalid = Balance::from_value(5);
81    /// ```
82    pub fn from_value(value: i8) -> Self {
83        match value {
84            -4 => Balance::TopLeft,
85            -3 => Balance::Top,
86            -2 => Balance::TopRight,
87            -1 => Balance::Left,
88            0 => Balance::Center,
89            1 => Balance::Right,
90            2 => Balance::BottomLeft,
91            3 => Balance::Bottom,
92            4 => Balance::BottomRight,
93            _ => panic!("Invalid value"),
94        }
95    }
96
97    /// Calculates the scalar magnitude squared for the vector representation
98    /// of the current `Balance` position within the grid.
99    ///
100    /// The scalar magnitude squared is defined as `x^2 + y^2`, where `(x, y)`
101    /// are the coordinates of the position.
102    ///
103    /// # Returns
104    ///
105    /// An `i8` value representing the scalar magnitude squared of the position.
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use balanced_direction::Balance;
111    ///
112    /// let position = Balance::TopLeft;
113    /// assert_eq!(position.to_scalar(), 2);
114    ///
115    /// let center = Balance::Center;
116    /// assert_eq!(center.to_scalar(), 0);
117    /// ```
118    pub fn to_scalar(self) -> i8 {
119        let (x, y) = self.to_vector();
120        x * x + y * y
121    }
122
123    /// Calculates the Euclidean (or absolute) magnitude of the vector representation
124    /// of the current `Balance` position.
125    ///
126    /// The magnitude is defined as the square root of the scalar magnitude squared (`√(x² + y²)`),
127    /// where `(x, y)` are the coordinates of the position.
128    ///
129    /// # Returns
130    ///
131    /// A `f64` value representing the Euclidean magnitude of the position with low precision (but fast) calculus.
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// use balanced_direction::Balance;
137    ///
138    /// let position = Balance::TopLeft;
139    /// assert_eq!(position.to_magnitude(), 2.0f64.sqrt());
140    ///
141    /// let center = Balance::Center;
142    /// assert_eq!(center.to_magnitude(), 0.0);
143    /// ```
144    pub fn to_magnitude(self) -> f64 {
145        if self.is_corner() {
146            core::f64::consts::SQRT_2
147        } else if self.is_edge() {
148            1.0
149        } else {
150            0.0
151        }
152    }
153
154    /// Converts the current `Balance` position into its corresponding
155    /// angle in degrees in a Cartesian coordinate system.
156    ///
157    /// The angle is calculated in a counter-clockwise direction starting from
158    /// the positive x-axis, with `(-y, x)` treated as the vector
159    /// direction. The angle is returned in the range `[-180.0, 180.0]` degrees.
160    ///
161    /// # Returns
162    ///
163    /// A `f32` value representing the angle in degrees.
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// use balanced_direction::Balance;
169    ///
170    /// let position = Balance::Top;
171    /// assert_eq!(position.to_angle(), 90.0);
172    /// ```
173    pub fn to_angle(self) -> f32 {
174        #[allow(unused_imports)]
175        use micromath::F32Ext;
176        let (x, y) = self.to_vector();
177        let angle = (-y as f32).atan2(x as f32);
178        angle.to_degrees()
179    }
180
181    /// Constructs a `Balance` enum variant based on the given angle in degrees.
182    ///
183    /// The angle is treated in the Cartesian coordinate system, where:
184    /// - `0` degrees corresponds to `Balance::Right` (positive x-axis),
185    /// - Positive angles proceed counterclockwise, and negative angles proceed clockwise,
186    /// - The `angle` is normalized into the range `[-180.0, 180.0]` and converted into
187    ///   the nearest discrete position `(x, y)` on the 3x3 grid.
188    ///
189    /// # Parameters
190    ///
191    /// - `angle`: A `f32` value representing the angle in degrees.
192    ///
193    /// # Returns
194    ///
195    /// A `Balance` enum variant corresponding to the direction indicated by the angle.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// use balanced_direction::Balance;
201    ///
202    /// let balance = Balance::from_angle(45.0);
203    /// assert_eq!(balance, Balance::TopRight);
204    ///
205    /// let balance = Balance::from_angle(-135.0);
206    /// assert_eq!(balance, Balance::BottomLeft);
207    /// ```
208    pub fn from_angle(angle: f32) -> Self {
209        #[allow(unused_imports)]
210        use micromath::F32Ext;
211        let angle = angle.to_radians();
212        let x = angle.cos();
213        let y = -angle.sin();
214        let (x, y) = (x.round() as i8, y.round() as i8);
215        Self::from_vector(x, y)
216    }
217
218    /// Converts the current `Balance` variant into a 2D vector `(i8, i8)` representing its coordinates.
219    ///
220    /// # Returns
221    ///
222    /// A tuple `(i8, i8)` representing the position in the 3x3 grid.
223    ///
224    /// # Examples
225    ///
226    /// ```
227    /// use balanced_direction::Balance;
228    ///
229    /// let position = Balance::TopLeft;
230    /// assert_eq!(position.to_vector(), (-1, -1));
231    ///
232    /// let center = Balance::Center;
233    /// assert_eq!(center.to_vector(), (0, 0));
234    /// ```
235    pub fn to_vector(self) -> (i8, i8) {
236        (self.x(), self.y())
237    }
238
239    /// Converts a pair of integers `(a, b)` into the corresponding `Balance` variant.
240    ///
241    /// # Parameters
242    ///
243    /// - `a`: The x-coordinate in the 2D grid, expected to be in the range `-1..=1`.
244    /// - `b`: The y-coordinate in the 2D grid, expected to be in the range `-1..=1`.
245    ///
246    /// # Returns
247    ///
248    /// The `Balance` variant that corresponds to the provided `(a, b)` coordinates.
249    ///
250    /// # Panics
251    ///
252    /// Panics if the provided `(a, b)` pair does not correspond to a valid `Balance` variant.
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use balanced_direction::Balance;
258    ///
259    /// let balance = Balance::from_vector(-1, -1);
260    /// assert_eq!(balance, Balance::TopLeft);
261    ///
262    /// let balance = Balance::from_vector(0, 1);
263    /// assert_eq!(balance, Balance::Bottom);
264    /// ```
265    pub fn from_vector(a: i8, b: i8) -> Self {
266        match (a, b) {
267            (-1, -1) => Balance::TopLeft,
268            (0, -1) => Balance::Top,
269            (1, -1) => Balance::TopRight,
270            (-1, 0) => Balance::Left,
271            (0, 0) => Balance::Center,
272            (1, 0) => Balance::Right,
273            (-1, 1) => Balance::BottomLeft,
274            (0, 1) => Balance::Bottom,
275            (1, 1) => Balance::BottomRight,
276            _ => panic!("Invalid vector"),
277        }
278    }
279}