Skip to main content

cotis_utils/
math.rs

1#[cfg(feature = "serialization")]
2use serde::{Deserialize, Serialize};
3
4/// 2D vector in Cotis pixel space.
5///
6/// Coordinates are interpreted as screen-space values (origin in the top-left,
7/// positive Y downward) by the default Cotis layout/render pipeline.
8///
9/// # Serialization
10///
11/// When the `serialization` feature is enabled this type derives
12/// `serde::Serialize` and `serde::Deserialize`.
13#[derive(Debug, Clone, Copy, PartialEq, Default)]
14#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
15pub struct Vector2 {
16    /// Horizontal component in pixels.
17    pub x: f32,
18    /// Vertical component in pixels.
19    pub y: f32,
20}
21
22impl Vector2 {
23    /// Creates a new vector from `x` and `y` components.
24    ///
25    /// # Examples
26    ///
27    /// ```rust
28    /// use cotis_utils::math::Vector2;
29    ///
30    /// let p = Vector2::new(10.0, 20.0);
31    /// assert_eq!(p.x, 10.0);
32    /// assert_eq!(p.y, 20.0);
33    /// ```
34    pub fn new(x: f32, y: f32) -> Self {
35        Self { x, y }
36    }
37}
38
39impl From<(f32, f32)> for Vector2 {
40    fn from(value: (f32, f32)) -> Self {
41        Self::new(value.0, value.1)
42    }
43}
44
45/// Width/height pair in pixels.
46///
47/// # Serialization
48///
49/// When the `serialization` feature is enabled this type derives
50/// `serde::Serialize` and `serde::Deserialize`.
51#[derive(Debug, Clone, Copy, PartialEq, Default)]
52#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
53pub struct Dimensions {
54    /// Width in pixels.
55    pub width: f32,
56    /// Height in pixels.
57    pub height: f32,
58}
59
60impl Dimensions {
61    /// Creates dimensions from width and height in pixels.
62    ///
63    /// # Examples
64    ///
65    /// ```rust
66    /// use cotis_utils::math::Dimensions;
67    ///
68    /// let size = Dimensions::new(800.0, 600.0);
69    /// assert_eq!(size.width, 800.0);
70    /// assert_eq!(size.height, 600.0);
71    /// ```
72    pub fn new(width: f32, height: f32) -> Self {
73        Self { width, height }
74    }
75}
76
77impl From<(f32, f32)> for Dimensions {
78    /// Builds [`Dimensions`] from `(width, height)`.
79    ///
80    /// # Examples
81    ///
82    /// ```rust
83    /// use cotis_utils::math::Dimensions;
84    ///
85    /// let size = Dimensions::from((320.0, 240.0));
86    /// assert_eq!(size.width, 320.0);
87    /// assert_eq!(size.height, 240.0);
88    /// ```
89    fn from(value: (f32, f32)) -> Self {
90        Self::new(value.0, value.1)
91    }
92}
93
94/// Axis-aligned rectangle in Cotis pixel space.
95///
96/// This struct is commonly used for hit-testing, clipping and debug overlays.
97///
98/// # Serialization
99///
100/// When the `serialization` feature is enabled this type derives
101/// `serde::Serialize` and `serde::Deserialize`.
102#[derive(Debug, Clone, Copy, PartialEq, Default)]
103#[cfg_attr(
104    feature = "serialization",
105    derive(serde::Serialize, serde::Deserialize)
106)]
107pub struct BoundingBox {
108    /// Left coordinate in pixels.
109    pub x: f32,
110    /// Top coordinate in pixels.
111    pub y: f32,
112    /// Width in pixels.
113    pub width: f32,
114    /// Height in pixels.
115    pub height: f32,
116}
117
118impl BoundingBox {
119    /// Creates a new bounding box in pixel coordinates.
120    ///
121    /// # Examples
122    ///
123    /// ```rust
124    /// use cotis_utils::math::BoundingBox;
125    ///
126    /// let bounds = BoundingBox::new(8.0, 16.0, 100.0, 40.0);
127    /// assert_eq!(bounds.width, 100.0);
128    /// assert_eq!(bounds.height, 40.0);
129    /// ```
130    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
131        Self {
132            x,
133            y,
134            width,
135            height,
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn vector2_and_dimensions_from_tuple() {
146        let vector = Vector2::from((3.0, 4.0));
147        assert_eq!(vector.x, 3.0);
148        assert_eq!(vector.y, 4.0);
149
150        let size = Dimensions::from((800.0, 600.0));
151        assert_eq!(size.width, 800.0);
152        assert_eq!(size.height, 600.0);
153    }
154
155    #[cfg(feature = "serialization")]
156    #[test]
157    fn math_types_roundtrip_through_json() {
158        use serde_json;
159
160        let vector = Vector2::new(1.0, 2.0);
161        let decoded: Vector2 =
162            serde_json::from_str(&serde_json::to_string(&vector).unwrap()).unwrap();
163        assert_eq!(decoded, vector);
164
165        let size = Dimensions::new(10.0, 20.0);
166        let decoded: Dimensions =
167            serde_json::from_str(&serde_json::to_string(&size).unwrap()).unwrap();
168        assert_eq!(decoded, size);
169
170        let bounds = BoundingBox::new(1.0, 2.0, 3.0, 4.0);
171        let decoded: BoundingBox =
172            serde_json::from_str(&serde_json::to_string(&bounds).unwrap()).unwrap();
173        assert_eq!(decoded, bounds);
174    }
175}