microcad_core/geo2d/
bounds.rs1use geo::{AffineOps, AffineTransform, CoordsIter, coord};
7
8use crate::{Scalar, Size2, Transformed2D, Vec2, geo2d::Rect, mat3_to_affine_transform};
9
10#[derive(Debug, Default, Clone)]
12pub struct Bounds2D(Option<Rect>);
13
14impl Bounds2D {
15 pub fn new(min: Vec2, max: Vec2) -> Self {
17 let min_x = min.x.min(max.x);
18 let min_y = min.y.min(max.y);
19 let max_x = min.x.max(max.x);
20 let max_y = min.y.max(max.y);
21
22 Self(Some(Rect::new(
23 coord! { x: min_x, y: min_y},
24 coord! { x: max_x, y: max_y},
25 )))
26 }
27
28 pub fn is_valid(&self) -> bool {
30 self.0.is_some()
31 }
32
33 pub fn min(&self) -> Option<Vec2> {
35 self.0.as_ref().map(|s| Vec2::new(s.min().x, s.min().y))
36 }
37
38 pub fn max(&self) -> Option<Vec2> {
40 self.0.as_ref().map(|s| Vec2::new(s.max().x, s.max().y))
41 }
42
43 pub fn width(&self) -> Scalar {
45 self.0.as_ref().map(|r| r.width()).unwrap_or_default()
46 }
47
48 pub fn height(&self) -> Scalar {
50 self.0.as_ref().map(|r| r.height()).unwrap_or_default()
51 }
52
53 pub fn rect(&self) -> &Option<Rect> {
55 &self.0
56 }
57
58 pub fn enlarge(&self, factor: Scalar) -> Self {
60 Self(self.0.map(|rect| {
61 let c = rect.center();
62 let s: geo::Coord = (rect.width(), rect.height()).into();
63 let s = s * 0.5 * (1.0 + factor);
64 Rect::new(c - s, c + s)
65 }))
66 }
67
68 pub fn extend(self, other: Bounds2D) -> Self {
70 match (self.0, other.0) {
71 (None, None) => Self(None),
72 (None, Some(r)) | (Some(r), None) => Self(Some(r)),
73 (Some(rect1), Some(rect2)) => Self::new(
74 Vec2::new(
75 rect1.min().x.min(rect2.min().x),
76 rect1.min().y.min(rect2.min().y),
77 ),
78 Vec2::new(
79 rect1.max().x.max(rect2.max().x),
80 rect1.max().y.max(rect2.max().y),
81 ),
82 ),
83 }
84 }
85
86 pub fn extend_by_point(&mut self, p: Vec2) {
88 match &mut self.0 {
89 Some(rect) => {
90 *rect = Rect::new(
91 coord! {
92 x: rect.min().x.min(p.x),
93 y: rect.min().y.min(p.y),
94 },
95 coord! {
96 x: rect.max().x.max(p.x),
97 y: rect.max().y.max(p.y),
98 },
99 )
100 }
101 None => *self = Self::new(p, p),
102 }
103 }
104}
105
106impl AffineOps<Scalar> for Bounds2D {
107 fn affine_transform(&self, transform: &AffineTransform<Scalar>) -> Self {
108 match &self.0 {
109 Some(rect) => rect
110 .coords_iter()
111 .fold(Bounds2D::default(), |mut bounds, p| {
112 let p = transform.apply(p);
113 bounds.extend_by_point(Vec2::new(p.x, p.y));
114 bounds
115 }),
116 None => Self(None),
117 }
118 }
119
120 fn affine_transform_mut(&mut self, transform: &AffineTransform<Scalar>) {
121 if let Some(rect) = &mut self.0 {
122 rect.affine_transform_mut(transform)
123 }
124 }
125}
126
127impl Transformed2D for Bounds2D {
128 fn transformed_2d(&self, _: &crate::RenderResolution, mat: &crate::Mat3) -> Self {
129 self.affine_transform(&mat3_to_affine_transform(mat))
130 }
131}
132
133impl From<Option<Rect>> for Bounds2D {
134 fn from(rect: Option<Rect>) -> Self {
135 match rect {
136 Some(rect) => Self::new(rect.min().x_y().into(), rect.max().x_y().into()),
137 None => Self(None),
138 }
139 }
140}
141
142impl From<Size2> for Bounds2D {
143 fn from(value: Size2) -> Self {
144 Self::new(Vec2::new(0.0, 0.0), Vec2::new(value.width, value.height))
145 }
146}
147
148impl std::fmt::Display for Bounds2D {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 match self.0 {
151 Some(rect) => write!(
152 f,
153 "[{min:?}, {max:?}]",
154 min = rect.min().x_y(),
155 max = rect.max().x_y()
156 ),
157 None => write!(f, "[no bounds]"),
158 }
159 }
160}
161
162pub trait FetchBounds2D {
164 fn fetch_bounds_2d(&self) -> Bounds2D;
166}
167
168#[test]
169fn bounds_2d_test() {
170 let bounds1 = Bounds2D::new(Vec2::new(0.0, 1.0), Vec2::new(2.0, 3.0));
171 let bounds2 = Bounds2D::new(Vec2::new(4.0, 5.0), Vec2::new(6.0, 7.0));
172
173 let bounds1 = bounds1.extend(bounds2);
174
175 assert_eq!(bounds1.min(), Some(Vec2::new(0.0, 1.0)));
176 assert_eq!(bounds1.max(), Some(Vec2::new(6.0, 7.0)));
177}