1 use crate::{Intersection, NegativeHemisphereError, Plane, Polygon};
2 
3 use euclid::{approxeq::ApproxEq, Rect, Scale, Transform3D, Trig, Vector3D};
4 use num_traits::{Float, One, Zero};
5 
6 use std::{fmt, iter, mem, ops};
7 
8 /// A helper object to clip polygons by a number of planes.
9 #[derive(Debug)]
10 pub struct Clipper<T, U, A> {
11     clips: Vec<Plane<T, U>>,
12     results: Vec<Polygon<T, U, A>>,
13     temp: Vec<Polygon<T, U, A>>,
14 }
15 
16 impl<
17         T: Copy
18             + fmt::Debug
19             + ApproxEq<T>
20             + ops::Sub<T, Output = T>
21             + ops::Add<T, Output = T>
22             + ops::Mul<T, Output = T>
23             + ops::Div<T, Output = T>
24             + Zero
25             + One
26             + Float,
27         U: fmt::Debug,
28         A: Copy + fmt::Debug,
29     > Clipper<T, U, A>
30 {
31     /// Create a new clipper object.
new() -> Self32     pub fn new() -> Self {
33         Clipper {
34             clips: Vec::new(),
35             results: Vec::new(),
36             temp: Vec::new(),
37         }
38     }
39 
40     /// Reset the clipper internals, but preserve the allocation.
reset(&mut self)41     pub fn reset(&mut self) {
42         self.clips.clear();
43     }
44 
45     /// Extract the clipping planes that define the frustum for a given transformation.
frustum_planes<V>( t: &Transform3D<T, U, V>, bounds: Option<Rect<T, V>>, ) -> Result<impl Iterator<Item = Plane<T, U>>, NegativeHemisphereError>46     pub fn frustum_planes<V>(
47         t: &Transform3D<T, U, V>,
48         bounds: Option<Rect<T, V>>,
49     ) -> Result<impl Iterator<Item = Plane<T, U>>, NegativeHemisphereError> {
50         let mw = Vector3D::new(t.m14, t.m24, t.m34);
51         let plane_positive = Plane::from_unnormalized(mw, t.m44)?;
52 
53         let bounds_iter_maybe = match bounds {
54             Some(bounds) => {
55                 let mx = Vector3D::new(t.m11, t.m21, t.m31);
56                 let left = bounds.origin.x;
57                 let plane_left =
58                     Plane::from_unnormalized(mx - mw * Scale::new(left), t.m41 - t.m44 * left)?;
59                 let right = bounds.origin.x + bounds.size.width;
60                 let plane_right =
61                     Plane::from_unnormalized(mw * Scale::new(right) - mx, t.m44 * right - t.m41)?;
62 
63                 let my = Vector3D::new(t.m12, t.m22, t.m32);
64                 let top = bounds.origin.y;
65                 let plane_top =
66                     Plane::from_unnormalized(my - mw * Scale::new(top), t.m42 - t.m44 * top)?;
67                 let bottom = bounds.origin.y + bounds.size.height;
68                 let plane_bottom =
69                     Plane::from_unnormalized(mw * Scale::new(bottom) - my, t.m44 * bottom - t.m42)?;
70 
71                 Some(
72                     plane_left
73                         .into_iter()
74                         .chain(plane_right)
75                         .chain(plane_top)
76                         .chain(plane_bottom),
77                 )
78             }
79             None => None,
80         };
81 
82         Ok(bounds_iter_maybe
83             .into_iter()
84             .flat_map(|pi| pi)
85             .chain(plane_positive))
86     }
87 
88     /// Add a clipping plane to the list. The plane will clip everything behind it,
89     /// where the direction is set by the plane normal.
add(&mut self, plane: Plane<T, U>)90     pub fn add(&mut self, plane: Plane<T, U>) {
91         self.clips.push(plane);
92     }
93 
94     /// Clip specified polygon by the contained planes, return the fragmented polygons.
clip(&mut self, polygon: Polygon<T, U, A>) -> &[Polygon<T, U, A>]95     pub fn clip(&mut self, polygon: Polygon<T, U, A>) -> &[Polygon<T, U, A>] {
96         log::debug!("\tClipping {:?}", polygon);
97         self.results.clear();
98         self.results.push(polygon);
99 
100         for clip in &self.clips {
101             self.temp.clear();
102             mem::swap(&mut self.results, &mut self.temp);
103 
104             for mut poly in self.temp.drain(..) {
105                 let dist = match poly.intersect_plane(clip) {
106                     Intersection::Inside(line) => {
107                         let (res1, res2) = poly.split_with_normal(&line, &clip.normal);
108                         self.results.extend(
109                             iter::once(poly)
110                                 .chain(res1)
111                                 .chain(res2)
112                                 .filter(|p| clip.signed_distance_sum_to(p) > T::zero()),
113                         );
114                         continue;
115                     }
116                     Intersection::Coplanar => {
117                         let ndot = poly.plane.normal.dot(clip.normal);
118                         clip.offset - ndot * poly.plane.offset
119                     }
120                     Intersection::Outside => clip.signed_distance_sum_to(&poly),
121                 };
122 
123                 if dist > T::zero() {
124                     self.results.push(poly);
125                 }
126             }
127         }
128 
129         &self.results
130     }
131 
132     /// Clip the primitive with the frustum of the specified transformation,
133     /// returning a sequence of polygons in the transformed space.
134     /// Returns None if the transformation can't be frustum clipped.
clip_transformed<'a, V>( &'a mut self, polygon: Polygon<T, U, A>, transform: &'a Transform3D<T, U, V>, bounds: Option<Rect<T, V>>, ) -> Result<impl 'a + Iterator<Item = Polygon<T, V, A>>, NegativeHemisphereError> where T: Trig, V: 'a + fmt::Debug,135     pub fn clip_transformed<'a, V>(
136         &'a mut self,
137         polygon: Polygon<T, U, A>,
138         transform: &'a Transform3D<T, U, V>,
139         bounds: Option<Rect<T, V>>,
140     ) -> Result<impl 'a + Iterator<Item = Polygon<T, V, A>>, NegativeHemisphereError>
141     where
142         T: Trig,
143         V: 'a + fmt::Debug,
144     {
145         let planes = Self::frustum_planes(transform, bounds)?;
146 
147         let old_count = self.clips.len();
148         self.clips.extend(planes);
149         self.clip(polygon);
150         // remove the frustum planes
151         while self.clips.len() > old_count {
152             self.clips.pop();
153         }
154 
155         let polys = self
156             .results
157             .drain(..)
158             .flat_map(move |poly| poly.transform(transform));
159         Ok(polys)
160     }
161 }
162