1use std::fmt;
9
10#[derive(Clone, Copy, PartialEq)]
15pub struct BBox {
16 pub x_min: f32,
17 pub y_min: f32,
18 pub x_max: f32,
19 pub y_max: f32,
20}
21
22impl BBox {
23 #[must_use]
25 pub fn new(x_min: f32, y_min: f32, x_max: f32, y_max: f32) -> Self {
26 Self {
27 x_min,
28 y_min,
29 x_max,
30 y_max,
31 }
32 }
33
34 #[must_use]
36 pub fn width(&self) -> f32 {
37 self.x_max - self.x_min
38 }
39
40 #[must_use]
42 pub fn height(&self) -> f32 {
43 self.y_max - self.y_min
44 }
45
46 #[must_use]
48 pub fn area(&self) -> f32 {
49 self.width() * self.height()
50 }
51
52 #[must_use]
54 pub fn center(&self) -> Point2 {
55 Point2 {
56 x: (self.x_min + self.x_max) * 0.5,
57 y: (self.y_min + self.y_max) * 0.5,
58 }
59 }
60
61 #[must_use]
63 pub fn iou(&self, other: &Self) -> f32 {
64 let ix_min = self.x_min.max(other.x_min);
65 let iy_min = self.y_min.max(other.y_min);
66 let ix_max = self.x_max.min(other.x_max);
67 let iy_max = self.y_max.min(other.y_max);
68
69 let iw = (ix_max - ix_min).max(0.0);
70 let ih = (iy_max - iy_min).max(0.0);
71 let intersection = iw * ih;
72
73 let union = self.area() + other.area() - intersection;
74 if union <= 0.0 {
75 return 0.0;
76 }
77 intersection / union
78 }
79}
80
81impl fmt::Debug for BBox {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(
84 f,
85 "BBox([{:.3}, {:.3}] -> [{:.3}, {:.3}])",
86 self.x_min, self.y_min, self.x_max, self.y_max
87 )
88 }
89}
90
91#[derive(Clone, Copy, PartialEq)]
93pub struct Point2 {
94 pub x: f32,
95 pub y: f32,
96}
97
98impl Point2 {
99 #[must_use]
101 pub fn new(x: f32, y: f32) -> Self {
102 Self { x, y }
103 }
104
105 #[must_use]
107 pub fn distance_to(&self, other: &Self) -> f32 {
108 let dx = self.x - other.x;
109 let dy = self.y - other.y;
110 (dx * dx + dy * dy).sqrt()
111 }
112}
113
114impl fmt::Debug for Point2 {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "Point2({:.4}, {:.4})", self.x, self.y)
117 }
118}
119
120#[derive(Clone, Debug, PartialEq)]
125pub struct Polygon {
126 pub vertices: Vec<Point2>,
128}
129
130impl Polygon {
131 #[must_use]
133 pub fn new(vertices: Vec<Point2>) -> Self {
134 Self { vertices }
135 }
136
137 #[must_use]
139 pub fn len(&self) -> usize {
140 self.vertices.len()
141 }
142
143 #[must_use]
145 pub fn is_empty(&self) -> bool {
146 self.vertices.is_empty()
147 }
148}
149
150#[derive(Clone, Copy, PartialEq)]
161pub struct AffineTransform2D {
162 pub m: [f64; 6],
164}
165
166impl AffineTransform2D {
167 pub const IDENTITY: Self = Self {
169 m: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
170 };
171
172 #[must_use]
174 pub fn new(a: f64, b: f64, tx: f64, c: f64, d: f64, ty: f64) -> Self {
175 Self {
176 m: [a, b, tx, c, d, ty],
177 }
178 }
179
180 #[must_use]
184 pub fn apply(&self, p: Point2) -> Point2 {
185 let x = p.x as f64;
186 let y = p.y as f64;
187 Point2 {
188 x: (self.m[0] * x + self.m[1] * y + self.m[2]) as f32,
189 y: (self.m[3] * x + self.m[4] * y + self.m[5]) as f32,
190 }
191 }
192
193 #[must_use]
199 pub fn apply_bbox(&self, bbox: BBox) -> BBox {
200 let corners = [
201 self.apply(Point2::new(bbox.x_min, bbox.y_min)),
202 self.apply(Point2::new(bbox.x_max, bbox.y_min)),
203 self.apply(Point2::new(bbox.x_max, bbox.y_max)),
204 self.apply(Point2::new(bbox.x_min, bbox.y_max)),
205 ];
206 BBox {
207 x_min: corners.iter().map(|c| c.x).fold(f32::INFINITY, f32::min),
208 y_min: corners.iter().map(|c| c.y).fold(f32::INFINITY, f32::min),
209 x_max: corners
210 .iter()
211 .map(|c| c.x)
212 .fold(f32::NEG_INFINITY, f32::max),
213 y_max: corners
214 .iter()
215 .map(|c| c.y)
216 .fold(f32::NEG_INFINITY, f32::max),
217 }
218 }
219
220 #[must_use]
224 pub fn then(&self, other: &Self) -> Self {
225 let a = self.m;
226 let b = other.m;
227 Self {
228 m: [
229 b[0] * a[0] + b[1] * a[3],
230 b[0] * a[1] + b[1] * a[4],
231 b[0] * a[2] + b[1] * a[5] + b[2],
232 b[3] * a[0] + b[4] * a[3],
233 b[3] * a[1] + b[4] * a[4],
234 b[3] * a[2] + b[4] * a[5] + b[5],
235 ],
236 }
237 }
238}
239
240impl fmt::Debug for AffineTransform2D {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 write!(
243 f,
244 "AffineTransform2D([{:.4}, {:.4}, {:.4}; {:.4}, {:.4}, {:.4}])",
245 self.m[0], self.m[1], self.m[2], self.m[3], self.m[4], self.m[5]
246 )
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn bbox_center() {
256 let b = BBox::new(0.1, 0.2, 0.5, 0.8);
257 let c = b.center();
258 assert!((c.x - 0.3).abs() < 1e-6);
259 assert!((c.y - 0.5).abs() < 1e-6);
260 }
261
262 #[test]
263 fn bbox_iou_identical() {
264 let b = BBox::new(0.0, 0.0, 0.5, 0.5);
265 assert!((b.iou(&b) - 1.0).abs() < 1e-6);
266 }
267
268 #[test]
269 fn bbox_iou_disjoint() {
270 let a = BBox::new(0.0, 0.0, 0.2, 0.2);
271 let b = BBox::new(0.5, 0.5, 1.0, 1.0);
272 assert!((a.iou(&b)).abs() < 1e-6);
273 }
274
275 #[test]
276 fn affine_identity() {
277 let t = AffineTransform2D::IDENTITY;
278 let p = Point2::new(0.3, 0.7);
279 let q = t.apply(p);
280 assert!((q.x - p.x).abs() < 1e-6);
281 assert!((q.y - p.y).abs() < 1e-6);
282 }
283
284 #[test]
285 fn affine_translation() {
286 let t = AffineTransform2D::new(1.0, 0.0, 0.1, 0.0, 1.0, 0.2);
287 let p = Point2::new(0.3, 0.4);
288 let q = t.apply(p);
289 assert!((q.x - 0.4).abs() < 1e-5);
290 assert!((q.y - 0.6).abs() < 1e-5);
291 }
292
293 #[test]
294 fn affine_compose_identity() {
295 let t = AffineTransform2D::new(2.0, 0.0, 0.0, 0.0, 3.0, 0.0);
296 let composed = t.then(&AffineTransform2D::IDENTITY);
297 assert_eq!(t.m, composed.m);
298 }
299}