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}