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}