maker_panel/features/
negative.rs

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