Trait-ify the 2D clipper

This commit is contained in:
Patrick Walton 2019-02-01 17:27:23 -08:00
parent df59fd9792
commit 805f0c9fa7
2 changed files with 136 additions and 113 deletions

View File

@ -105,31 +105,7 @@ impl<'a> RectClipper<'a> {
#[derive(Clone, Copy, Debug)]
struct Edge(LineSegmentF32);
impl Edge {
#[inline]
fn left(rect: &Rect<f32>) -> Edge {
Edge(LineSegmentF32::new(&Point2DF32::from_euclid(rect.bottom_left()),
&Point2DF32::from_euclid(rect.origin)))
}
#[inline]
fn top(rect: &Rect<f32>) -> Edge {
Edge(LineSegmentF32::new(&Point2DF32::from_euclid(rect.origin),
&Point2DF32::from_euclid(rect.top_right())))
}
#[inline]
fn right(rect: &Rect<f32>) -> Edge {
Edge(LineSegmentF32::new(&Point2DF32::from_euclid(rect.top_right()),
&Point2DF32::from_euclid(rect.bottom_right())))
}
#[inline]
fn bottom(rect: &Rect<f32>) -> Edge {
Edge(LineSegmentF32::new(&Point2DF32::from_euclid(rect.bottom_right()),
&Point2DF32::from_euclid(rect.bottom_left())))
}
impl TEdge for Edge {
#[inline]
fn point_is_inside(&self, point: &Point2DF32) -> bool {
let area = (self.0.to() - self.0.from()).det(*point - self.0.from());
@ -137,25 +113,6 @@ impl Edge {
area >= 0.0
}
fn trivially_test_segment(&self, segment: &Segment) -> EdgeRelativeLocation {
let from_inside = self.point_is_inside(&segment.baseline.from());
//println!("point {:?} inside {:?}: {:?}", segment.baseline.from(), self, from_inside);
if from_inside != self.point_is_inside(&segment.baseline.to()) {
return EdgeRelativeLocation::Intersecting;
}
if !segment.is_line() {
if from_inside != self.point_is_inside(&segment.ctrl.from()) {
return EdgeRelativeLocation::Intersecting;
}
if !segment.is_quadratic() {
if from_inside != self.point_is_inside(&segment.ctrl.to()) {
return EdgeRelativeLocation::Intersecting;
}
}
}
if from_inside { EdgeRelativeLocation::Inside } else { EdgeRelativeLocation::Outside }
}
fn intersect_segment(&self, segment: &Segment) -> ArrayVec<[f32; 3]> {
if segment.is_line() {
return self.intersect_line_segment(&segment.baseline);
@ -183,6 +140,32 @@ impl Edge {
const EPSILON: f32 = 0.0001;
}
}
impl Edge {
#[inline]
fn left(rect: &Rect<f32>) -> Edge {
Edge(LineSegmentF32::new(&Point2DF32::from_euclid(rect.bottom_left()),
&Point2DF32::from_euclid(rect.origin)))
}
#[inline]
fn top(rect: &Rect<f32>) -> Edge {
Edge(LineSegmentF32::new(&Point2DF32::from_euclid(rect.origin),
&Point2DF32::from_euclid(rect.top_right())))
}
#[inline]
fn right(rect: &Rect<f32>) -> Edge {
Edge(LineSegmentF32::new(&Point2DF32::from_euclid(rect.top_right()),
&Point2DF32::from_euclid(rect.bottom_right())))
}
#[inline]
fn bottom(rect: &Rect<f32>) -> Edge {
Edge(LineSegmentF32::new(&Point2DF32::from_euclid(rect.bottom_right()),
&Point2DF32::from_euclid(rect.bottom_left())))
}
fn intersect_line_segment(&self, segment: &LineSegmentF32) -> ArrayVec<[f32; 3]> {
let mut results = ArrayVec::new();
@ -231,20 +214,120 @@ impl Edge {
}
}
pub(crate) struct ContourClipper {
trait TEdge {
fn point_is_inside(&self, point: &Point2DF32) -> bool;
fn intersect_segment(&self, segment: &Segment) -> ArrayVec<[f32; 3]>;
fn trivially_test_segment(&self, segment: &Segment) -> EdgeRelativeLocation {
let from_inside = self.point_is_inside(&segment.baseline.from());
//println!("point {:?} inside {:?}: {:?}", segment.baseline.from(), self, from_inside);
if from_inside != self.point_is_inside(&segment.baseline.to()) {
return EdgeRelativeLocation::Intersecting;
}
if !segment.is_line() {
if from_inside != self.point_is_inside(&segment.ctrl.from()) {
return EdgeRelativeLocation::Intersecting;
}
if !segment.is_quadratic() {
if from_inside != self.point_is_inside(&segment.ctrl.to()) {
return EdgeRelativeLocation::Intersecting;
}
}
}
if from_inside { EdgeRelativeLocation::Inside } else { EdgeRelativeLocation::Outside }
}
}
trait ContourClipper where Self::Edge: TEdge {
type Edge;
fn contour_mut(&mut self) -> &mut Contour;
fn clip_against(&mut self, edge: Self::Edge) {
let input = mem::replace(self.contour_mut(), Contour::new());
for mut segment in input.iter() {
// Easy cases.
match edge.trivially_test_segment(&segment) {
EdgeRelativeLocation::Outside => continue,
EdgeRelativeLocation::Inside => {
//println!("trivial test inside, pushing segment");
push_segment(self.contour_mut(), &segment);
continue;
}
EdgeRelativeLocation::Intersecting => {}
}
// We have a potential intersection.
//println!("potential intersection: {:?} edge: {:?}", segment, edge);
let mut starts_inside = edge.point_is_inside(&segment.baseline.from());
let intersection_ts = edge.intersect_segment(&segment);
let mut last_t = 0.0;
//println!("... intersections: {:?}", intersection_ts);
for t in intersection_ts {
let (before_split, after_split) = segment.split((t - last_t) / (1.0 - last_t));
// Push the split segment if appropriate.
/*println!("... ... edge={:?} before_split={:?} t={:?} starts_inside={:?}",
edge.0,
before_split,
t,
starts_inside);*/
if starts_inside {
//println!("... split segment case, pushing segment");
push_segment(self.contour_mut(), &before_split);
}
// We've now transitioned from inside to outside or vice versa.
starts_inside = !starts_inside;
last_t = t;
segment = after_split;
}
// No more intersections. Push the last segment if applicable.
if starts_inside {
//println!("... last segment case, pushing segment");
push_segment(self.contour_mut(), &segment);
}
}
fn push_segment(contour: &mut Contour, segment: &Segment) {
//println!("... push_segment({:?}, edge={:?}", segment, edge);
if let Some(last_position) = contour.last_position() {
if last_position != segment.baseline.from() {
// Add a line to join up segments.
contour.push_point(segment.baseline.from(), PointFlags::empty());
}
}
contour.push_segment(*segment);
}
}
}
pub(crate) struct ContourPolygonClipper {
clip_polygon: SmallVec<[Point2DF32; 4]>,
contour: Contour,
}
impl ContourClipper {
impl ContourClipper for ContourPolygonClipper {
type Edge = Edge;
#[inline]
pub(crate) fn new(clip_polygon: &[Point2DF32], contour: Contour) -> ContourClipper {
ContourClipper { clip_polygon: SmallVec::from_slice(clip_polygon), contour }
fn contour_mut(&mut self) -> &mut Contour {
&mut self.contour
}
}
impl ContourPolygonClipper {
#[inline]
pub(crate) fn new(clip_polygon: &[Point2DF32], contour: Contour) -> ContourPolygonClipper {
ContourPolygonClipper { clip_polygon: SmallVec::from_slice(clip_polygon), contour }
}
#[inline]
pub(crate) fn from_rect(clip_rect: &Rect<f32>, contour: Contour) -> ContourClipper {
ContourClipper::new(&[
pub(crate) fn from_rect(clip_rect: &Rect<f32>, contour: Contour) -> ContourPolygonClipper {
ContourPolygonClipper::new(&[
Point2DF32::from_euclid(clip_rect.origin),
Point2DF32::from_euclid(clip_rect.top_right()),
Point2DF32::from_euclid(clip_rect.bottom_right()),
@ -270,66 +353,6 @@ impl ContourClipper {
self.contour
}
fn clip_against(&mut self, edge: Edge) {
let input = mem::replace(&mut self.contour, Contour::new());
for mut segment in input.iter() {
// Easy cases.
match edge.trivially_test_segment(&segment) {
EdgeRelativeLocation::Outside => continue,
EdgeRelativeLocation::Inside => {
//println!("trivial test inside, pushing segment");
push_segment(&mut self.contour, &segment, edge);
continue;
}
EdgeRelativeLocation::Intersecting => {}
}
// We have a potential intersection.
//println!("potential intersection: {:?} edge: {:?}", segment, edge);
let mut starts_inside = edge.point_is_inside(&segment.baseline.from());
let intersection_ts = edge.intersect_segment(&segment);
let mut last_t = 0.0;
//println!("... intersections: {:?}", intersection_ts);
for t in intersection_ts {
let (before_split, after_split) = segment.split((t - last_t) / (1.0 - last_t));
// Push the split segment if appropriate.
/*println!("... ... edge={:?} before_split={:?} t={:?} starts_inside={:?}",
edge.0,
before_split,
t,
starts_inside);*/
if starts_inside {
//println!("... split segment case, pushing segment");
push_segment(&mut self.contour, &before_split, edge);
}
// We've now transitioned from inside to outside or vice versa.
starts_inside = !starts_inside;
last_t = t;
segment = after_split;
}
// No more intersections. Push the last segment if applicable.
if starts_inside {
//println!("... last segment case, pushing segment");
push_segment(&mut self.contour, &segment, edge);
}
}
fn push_segment(contour: &mut Contour, segment: &Segment, edge: Edge) {
//println!("... push_segment({:?}, edge={:?}", segment, edge);
if let Some(last_position) = contour.last_position() {
if last_position != segment.baseline.from() {
// Add a line to join up segments.
contour.push_point(segment.baseline.from(), PointFlags::empty());
}
}
contour.push_segment(*segment);
}
}
}
enum EdgeRelativeLocation {

View File

@ -10,7 +10,7 @@
//! A compressed in-memory representation of paths.
use crate::clip::ContourClipper;
use crate::clip::ContourPolygonClipper;
use crate::line_segment::LineSegmentF32;
use crate::monotonic::MonotonicConversionIter;
use crate::point::Point2DF32;
@ -130,7 +130,7 @@ impl Outline {
pub fn clip_against_polygon(&mut self, clip_polygon: &[Point2DF32]) {
for contour in mem::replace(&mut self.contours, vec![]) {
let contour = ContourClipper::new(clip_polygon, contour).clip();
let contour = ContourPolygonClipper::new(clip_polygon, contour).clip();
if !contour.is_empty() {
self.push_contour(contour);
}
@ -139,7 +139,7 @@ impl Outline {
pub fn clip_against_rect(&mut self, clip_rect: &Rect<f32>) {
for contour in mem::replace(&mut self.contours, vec![]) {
let contour = ContourClipper::from_rect(clip_rect, contour).clip();
let contour = ContourPolygonClipper::from_rect(clip_rect, contour).clip();
if !contour.is_empty() {
self.push_contour(contour);
}