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