Skip to main content

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    /// The x-coordinate.
9    pub x: f64,
10    /// The y-coordinate.
11    pub y: f64,
12}
13
14impl Point {
15    /// Creates a new `Point`.
16    pub fn new(x: f64, y: f64) -> Self {
17        Self { x, y }
18    }
19
20    /// Creates a `Point` at the origin (0, 0).
21    pub fn origin() -> Self {
22        Self::new(0.0, 0.0)
23    }
24
25    /// Calculates the Euclidean distance to another point.
26    pub fn distance_to(&self, other: &Point) -> f64 {
27        let dx = self.x - other.x;
28        let dy = self.y - other.y;
29        (dx * dx + dy * dy).sqrt()
30    }
31
32    /// Returns a new point translated by the given deltas.
33    pub fn translate(&self, dx: f64, dy: f64) -> Point {
34        Point::new(self.x + dx, self.y + dy)
35    }
36
37    /// Returns a new point scaled by the given factor.
38    pub fn scale(&self, factor: f64) -> Point {
39        Point::new(self.x * factor, self.y * factor)
40    }
41}
42
43impl Default for Point {
44    fn default() -> Self {
45        Self::origin()
46    }
47}
48
49impl std::ops::Add for Point {
50    type Output = Point;
51
52    fn add(self, other: Point) -> Point {
53        Point::new(self.x + other.x, self.y + other.y)
54    }
55}
56
57impl std::ops::Sub for Point {
58    type Output = Point;
59
60    fn sub(self, other: Point) -> Point {
61        Point::new(self.x - other.x, self.y - other.y)
62    }
63}
64
65/// 2D size
66#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
67pub struct Size {
68    /// The width.
69    pub width: f64,
70    /// The height.
71    pub height: f64,
72}
73
74impl Size {
75    /// Creates a new `Size`.
76    pub fn new(width: f64, height: f64) -> Self {
77        Self { width, height }
78    }
79
80    /// Creates a square `Size`.
81    pub fn square(size: f64) -> Self {
82        Self::new(size, size)
83    }
84
85    /// Calculates the area of the size.
86    pub fn area(&self) -> f64 {
87        self.width * self.height
88    }
89
90    /// Calculates the aspect ratio (width / height).
91    pub fn aspect_ratio(&self) -> f64 {
92        if self.height != 0.0 { self.width / self.height } else { f64::INFINITY }
93    }
94
95    /// Returns a new size scaled by the given factor.
96    pub fn scale(&self, factor: f64) -> Size {
97        Size::new(self.width * factor, self.height * factor)
98    }
99
100    /// Returns a new size scaled by the given factors for width and height.
101    pub fn scale_xy(&self, x_factor: f64, y_factor: f64) -> Size {
102        Size::new(self.width * x_factor, self.height * y_factor)
103    }
104}
105
106impl Default for Size {
107    fn default() -> Self {
108        Self::new(100.0, 50.0)
109    }
110}
111
112/// Rectangle defined by position and size
113#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
114pub struct Rect {
115    /// The origin point of the rectangle.
116    pub origin: Point,
117    /// The size of the rectangle.
118    pub size: Size,
119}
120
121impl Rect {
122    /// Creates a new `Rect`.
123    pub fn new(origin: Point, size: Size) -> Self {
124        Self { origin, size }
125    }
126
127    /// Creates a new `Rect` from x, y, width, and height.
128    pub fn from_xywh(x: f64, y: f64, width: f64, height: f64) -> Self {
129        Self::new(Point::new(x, y), Size::new(width, height))
130    }
131
132    /// Creates a new `Rect` from two points.
133    pub fn from_points(p1: Point, p2: Point) -> Self {
134        let min_x = p1.x.min(p2.x);
135        let min_y = p1.y.min(p2.y);
136        let max_x = p1.x.max(p2.x);
137        let max_y = p1.y.max(p2.y);
138
139        Self::from_xywh(min_x, min_y, max_x - min_x, max_y - min_y)
140    }
141
142    /// Gets the x-coordinate of the origin.
143    pub fn x(&self) -> f64 {
144        self.origin.x
145    }
146
147    /// Gets the y-coordinate of the origin.
148    pub fn y(&self) -> f64 {
149        self.origin.y
150    }
151
152    /// Gets the width of the rectangle.
153    pub fn width(&self) -> f64 {
154        self.size.width
155    }
156
157    /// Gets the height of the rectangle.
158    pub fn height(&self) -> f64 {
159        self.size.height
160    }
161
162    /// Gets the minimum x-coordinate.
163    pub fn min_x(&self) -> f64 {
164        self.origin.x
165    }
166
167    /// Gets the minimum y-coordinate.
168    pub fn min_y(&self) -> f64 {
169        self.origin.y
170    }
171
172    /// Gets the maximum x-coordinate.
173    pub fn max_x(&self) -> f64 {
174        self.origin.x + self.size.width
175    }
176
177    /// Gets the maximum y-coordinate.
178    pub fn max_y(&self) -> f64 {
179        self.origin.y + self.size.height
180    }
181
182    /// Gets the center point of the rectangle.
183    pub fn center(&self) -> Point {
184        Point::new(self.origin.x + self.size.width / 2.0, self.origin.y + self.size.height / 2.0)
185    }
186
187    /// Gets the top-left point.
188    pub fn top_left(&self) -> Point {
189        self.origin
190    }
191
192    /// Gets the top-right point.
193    pub fn top_right(&self) -> Point {
194        Point::new(self.max_x(), self.min_y())
195    }
196
197    /// Gets the bottom-left point.
198    pub fn bottom_left(&self) -> Point {
199        Point::new(self.min_x(), self.max_y())
200    }
201
202    /// Gets the bottom-right point.
203    pub fn bottom_right(&self) -> Point {
204        Point::new(self.max_x(), self.max_y())
205    }
206
207    /// Checks if a point is inside the rectangle.
208    pub fn contains_point(&self, point: Point) -> bool {
209        point.x >= self.min_x() && point.x <= self.max_x() && point.y >= self.min_y() && point.y <= self.max_y()
210    }
211
212    /// Checks if this rectangle intersects with another.
213    pub fn intersects(&self, other: &Rect) -> bool {
214        !(self.max_x() < other.min_x() || other.max_x() < self.min_x() || self.max_y() < other.min_y() || other.max_y() < self.min_y())
215    }
216
217    /// Returns the union of this rectangle and another.
218    pub fn union(&self, other: &Rect) -> Rect {
219        let min_x = self.min_x().min(other.min_x());
220        let min_y = self.min_y().min(other.min_y());
221        let max_x = self.max_x().max(other.max_x());
222        let max_y = self.max_y().max(other.max_y());
223
224        Rect::from_xywh(min_x, min_y, max_x - min_x, max_y - min_y)
225    }
226
227    /// Returns a new rectangle translated by the given deltas.
228    pub fn translate(&self, dx: f64, dy: f64) -> Rect {
229        Rect::new(self.origin.translate(dx, dy), self.size)
230    }
231
232    /// Returns a new rectangle scaled by the given factor.
233    pub fn scale(&self, factor: f64) -> Rect {
234        Rect::new(self.origin.scale(factor), self.size.scale(factor))
235    }
236
237    /// Returns a new rectangle expanded by the given margin on all sides.
238    pub fn expand(&self, margin: f64) -> Rect {
239        Rect::from_xywh(self.x() - margin, self.y() - margin, self.width() + 2.0 * margin, self.height() + 2.0 * margin)
240    }
241}
242
243impl Default for Rect {
244    fn default() -> Self {
245        Self::new(Point::default(), Size::default())
246    }
247}
248
249/// 2D transformation matrix
250#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
251pub struct Transform {
252    /// Scale factor in x direction.
253    pub a: f64,
254    /// Skew factor in y direction.
255    pub b: f64,
256    /// Skew factor in x direction.
257    pub c: f64,
258    /// Scale factor in y direction.
259    pub d: f64,
260    /// Translation in x direction.
261    pub e: f64,
262    /// Translation in y direction.
263    pub f: f64,
264}
265
266impl Transform {
267    /// Returns the identity transformation.
268    pub fn identity() -> Self {
269        Self { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: 0.0, f: 0.0 }
270    }
271
272    /// Creates a translation transformation.
273    pub fn translate(x: f64, y: f64) -> Self {
274        Self { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: x, f: y }
275    }
276
277    /// Creates a scale transformation.
278    pub fn scale(x: f64, y: f64) -> Self {
279        Self { a: x, b: 0.0, c: 0.0, d: y, e: 0.0, f: 0.0 }
280    }
281
282    /// Creates a rotation transformation.
283    pub fn rotate(angle: f64) -> Self {
284        let cos_a = angle.cos();
285        let sin_a = angle.sin();
286
287        Self { a: cos_a, b: sin_a, c: -sin_a, d: cos_a, e: 0.0, f: 0.0 }
288    }
289
290    /// Applies the transformation to a point.
291    pub fn transform_point(&self, point: Point) -> Point {
292        Point::new(self.a * point.x + self.c * point.y + self.e, self.b * point.x + self.d * point.y + self.f)
293    }
294
295    /// Composes this transformation with another.
296    pub fn compose(&self, other: &Transform) -> Transform {
297        Transform {
298            a: self.a * other.a + self.b * other.c,
299            b: self.a * other.b + self.b * other.d,
300            c: self.c * other.a + self.d * other.c,
301            d: self.c * other.b + self.d * other.d,
302            e: self.e * other.a + self.f * other.c + other.e,
303            f: self.e * other.b + self.f * other.d + other.f,
304        }
305    }
306}
307
308impl Default for Transform {
309    fn default() -> Self {
310        Self::identity()
311    }
312}