oak_visualize/geometry/
mod.rs

1#![doc = "Basic geometry types for visualization"]
2
3use serde::{Deserialize, Serialize};
4
5/// 2D point
6#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
7pub struct Point {
8    pub x: f64,
9    pub y: f64,
10}
11
12impl Point {
13    pub fn new(x: f64, y: f64) -> Self {
14        Self { x, y }
15    }
16
17    pub fn origin() -> Self {
18        Self::new(0.0, 0.0)
19    }
20
21    pub fn distance_to(&self, other: &Point) -> f64 {
22        let dx = self.x - other.x;
23        let dy = self.y - other.y;
24        (dx * dx + dy * dy).sqrt()
25    }
26
27    pub fn translate(&self, dx: f64, dy: f64) -> Point {
28        Point::new(self.x + dx, self.y + dy)
29    }
30
31    pub fn scale(&self, factor: f64) -> Point {
32        Point::new(self.x * factor, self.y * factor)
33    }
34}
35
36impl Default for Point {
37    fn default() -> Self {
38        Self::origin()
39    }
40}
41
42impl std::ops::Add for Point {
43    type Output = Point;
44
45    fn add(self, other: Point) -> Point {
46        Point::new(self.x + other.x, self.y + other.y)
47    }
48}
49
50impl std::ops::Sub for Point {
51    type Output = Point;
52
53    fn sub(self, other: Point) -> Point {
54        Point::new(self.x - other.x, self.y - other.y)
55    }
56}
57
58/// 2D size
59#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
60pub struct Size {
61    pub width: f64,
62    pub height: f64,
63}
64
65impl Size {
66    pub fn new(width: f64, height: f64) -> Self {
67        Self { width, height }
68    }
69
70    pub fn square(size: f64) -> Self {
71        Self::new(size, size)
72    }
73
74    pub fn area(&self) -> f64 {
75        self.width * self.height
76    }
77
78    pub fn aspect_ratio(&self) -> f64 {
79        if self.height != 0.0 { self.width / self.height } else { f64::INFINITY }
80    }
81
82    pub fn scale(&self, factor: f64) -> Size {
83        Size::new(self.width * factor, self.height * factor)
84    }
85
86    pub fn scale_xy(&self, x_factor: f64, y_factor: f64) -> Size {
87        Size::new(self.width * x_factor, self.height * y_factor)
88    }
89}
90
91impl Default for Size {
92    fn default() -> Self {
93        Self::new(100.0, 50.0)
94    }
95}
96
97/// Rectangle defined by position and size
98#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
99pub struct Rect {
100    pub origin: Point,
101    pub size: Size,
102}
103
104impl Rect {
105    pub fn new(origin: Point, size: Size) -> Self {
106        Self { origin, size }
107    }
108
109    pub fn from_xywh(x: f64, y: f64, width: f64, height: f64) -> Self {
110        Self::new(Point::new(x, y), Size::new(width, height))
111    }
112
113    pub fn from_points(p1: Point, p2: Point) -> Self {
114        let min_x = p1.x.min(p2.x);
115        let min_y = p1.y.min(p2.y);
116        let max_x = p1.x.max(p2.x);
117        let max_y = p1.y.max(p2.y);
118
119        Self::from_xywh(min_x, min_y, max_x - min_x, max_y - min_y)
120    }
121
122    pub fn x(&self) -> f64 {
123        self.origin.x
124    }
125
126    pub fn y(&self) -> f64 {
127        self.origin.y
128    }
129
130    pub fn width(&self) -> f64 {
131        self.size.width
132    }
133
134    pub fn height(&self) -> f64 {
135        self.size.height
136    }
137
138    pub fn min_x(&self) -> f64 {
139        self.origin.x
140    }
141
142    pub fn min_y(&self) -> f64 {
143        self.origin.y
144    }
145
146    pub fn max_x(&self) -> f64 {
147        self.origin.x + self.size.width
148    }
149
150    pub fn max_y(&self) -> f64 {
151        self.origin.y + self.size.height
152    }
153
154    pub fn center(&self) -> Point {
155        Point::new(self.origin.x + self.size.width / 2.0, self.origin.y + self.size.height / 2.0)
156    }
157
158    pub fn top_left(&self) -> Point {
159        self.origin
160    }
161
162    pub fn top_right(&self) -> Point {
163        Point::new(self.max_x(), self.min_y())
164    }
165
166    pub fn bottom_left(&self) -> Point {
167        Point::new(self.min_x(), self.max_y())
168    }
169
170    pub fn bottom_right(&self) -> Point {
171        Point::new(self.max_x(), self.max_y())
172    }
173
174    pub fn contains_point(&self, point: Point) -> bool {
175        point.x >= self.min_x() && point.x <= self.max_x() && point.y >= self.min_y() && point.y <= self.max_y()
176    }
177
178    pub fn intersects(&self, other: &Rect) -> bool {
179        !(self.max_x() < other.min_x()
180            || other.max_x() < self.min_x()
181            || self.max_y() < other.min_y()
182            || other.max_y() < self.min_y())
183    }
184
185    pub fn union(&self, other: &Rect) -> Rect {
186        let min_x = self.min_x().min(other.min_x());
187        let min_y = self.min_y().min(other.min_y());
188        let max_x = self.max_x().max(other.max_x());
189        let max_y = self.max_y().max(other.max_y());
190
191        Rect::from_xywh(min_x, min_y, max_x - min_x, max_y - min_y)
192    }
193
194    pub fn translate(&self, dx: f64, dy: f64) -> Rect {
195        Rect::new(self.origin.translate(dx, dy), self.size)
196    }
197
198    pub fn scale(&self, factor: f64) -> Rect {
199        Rect::new(self.origin.scale(factor), self.size.scale(factor))
200    }
201
202    pub fn expand(&self, margin: f64) -> Rect {
203        Rect::from_xywh(self.x() - margin, self.y() - margin, self.width() + 2.0 * margin, self.height() + 2.0 * margin)
204    }
205}
206
207impl Default for Rect {
208    fn default() -> Self {
209        Self::new(Point::default(), Size::default())
210    }
211}
212
213/// 2D transformation matrix
214#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
215pub struct Transform {
216    pub a: f64, // scale_x
217    pub b: f64, // skew_y
218    pub c: f64, // skew_x
219    pub d: f64, // scale_y
220    pub e: f64, // translate_x
221    pub f: f64, // translate_y
222}
223
224impl Transform {
225    pub fn identity() -> Self {
226        Self { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: 0.0, f: 0.0 }
227    }
228
229    pub fn translate(x: f64, y: f64) -> Self {
230        Self { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: x, f: y }
231    }
232
233    pub fn scale(x: f64, y: f64) -> Self {
234        Self { a: x, b: 0.0, c: 0.0, d: y, e: 0.0, f: 0.0 }
235    }
236
237    pub fn rotate(angle: f64) -> Self {
238        let cos_a = angle.cos();
239        let sin_a = angle.sin();
240
241        Self { a: cos_a, b: sin_a, c: -sin_a, d: cos_a, e: 0.0, f: 0.0 }
242    }
243
244    pub fn transform_point(&self, point: Point) -> Point {
245        Point::new(self.a * point.x + self.c * point.y + self.e, self.b * point.x + self.d * point.y + self.f)
246    }
247
248    pub fn compose(&self, other: &Transform) -> Transform {
249        Transform {
250            a: self.a * other.a + self.b * other.c,
251            b: self.a * other.b + self.b * other.d,
252            c: self.c * other.a + self.d * other.c,
253            d: self.c * other.b + self.d * other.d,
254            e: self.e * other.a + self.f * other.c + other.e,
255            f: self.e * other.b + self.f * other.d + other.f,
256        }
257    }
258}
259
260impl Default for Transform {
261    fn default() -> Self {
262        Self::identity()
263    }
264}