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}