balanced_direction/
conversions.rs

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