maker_panel/features/
rotate.rs

1use geo::{Coordinate, MultiPolygon};
2use std::fmt;
3
4/// A feature which is the origin-centered rotation of its contained geometry.
5#[derive(Debug, Clone)]
6pub struct Rotate<U = super::Unit> {
7    features: Vec<U>,
8    rotate: f64,
9}
10
11impl<U: super::Feature + fmt::Debug + Clone> Rotate<U> {
12    pub fn new(rotate: f64, features: Vec<U>) -> Self {
13        Self { features, rotate }
14    }
15}
16
17impl<U> fmt::Display for Rotate<U>
18where
19    U: super::Feature + fmt::Debug + Clone,
20{
21    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22        write!(f, "Rotate({:?}, {:?})", self.rotate, self.features)
23    }
24}
25
26impl<U> super::Feature for Rotate<U>
27where
28    U: super::Feature + fmt::Debug + Clone,
29{
30    fn name(&self) -> &'static str {
31        "rotate"
32    }
33
34    fn edge_union(&self) -> Option<MultiPolygon<f64>> {
35        use geo::algorithm::rotate::Rotate;
36
37        self.features
38            .iter()
39            .map(|f| match f.edge_union() {
40                Some(edge_geo) => Some(edge_geo.clone()),
41                None => None,
42            })
43            .filter(|f| f.is_some())
44            .map(|f| f.unwrap().rotate(self.rotate))
45            .fold(None, |mut acc, g| {
46                use geo_booleanop::boolean::BooleanOp;
47                if let Some(current) = acc {
48                    acc = Some(g.union(&current));
49                } else {
50                    acc = Some(g);
51                };
52                acc
53            })
54    }
55
56    fn edge_subtract(&self) -> Option<MultiPolygon<f64>> {
57        use geo::algorithm::rotate::Rotate;
58
59        self.features
60            .iter()
61            .map(|f| match f.edge_subtract() {
62                Some(edge_geo) => Some(edge_geo.clone()),
63                None => None,
64            })
65            .filter(|f| f.is_some())
66            .map(|f| f.unwrap().rotate(self.rotate))
67            .fold(None, |mut acc, g| {
68                use geo_booleanop::boolean::BooleanOp;
69                if let Some(current) = acc {
70                    acc = Some(g.union(&current));
71                } else {
72                    acc = Some(g);
73                };
74                acc
75            })
76    }
77
78    fn translate(&mut self, v: Coordinate<f64>) {
79        for e in self.features.iter_mut() {
80            e.translate(v);
81        }
82    }
83
84    fn interior(&self) -> Vec<super::InnerAtom> {
85        vec![]
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::features::{Feature, Rect};
93
94    #[test]
95    fn identity() {
96        let a = Rotate::new(0.0, vec![Rect::with_center([0., 0.].into(), 2., 3.)]);
97
98        assert_eq!(a.edge_subtract(), None,);
99
100        assert_eq!(
101            a.edge_union(),
102            Some(geo::MultiPolygon::from(vec![geo::Polygon::new(
103                geo::LineString(vec![
104                    geo::Coordinate { x: -1.0, y: -1.5 },
105                    geo::Coordinate { x: -1.0, y: 1.5 },
106                    geo::Coordinate { x: 1.0, y: 1.5 },
107                    geo::Coordinate { x: 1.0, y: -1.5 },
108                ]),
109                vec![],
110            )])),
111        );
112    }
113
114    #[test]
115    fn basic() {
116        let a = Rotate::new(90.0, vec![Rect::with_center([0., 0.].into(), 1., 3.)]);
117
118        use geo::bounding_rect::BoundingRect;
119        let bounds = a.edge_union().unwrap().bounding_rect().unwrap();
120        assert!(bounds.width() > 2.99);
121        assert!(bounds.height() < 1.01);
122    }
123}