Use a fast path for axis aligned clipping

This commit is contained in:
Patrick Walton 2019-02-01 17:49:03 -08:00
parent 805f0c9fa7
commit 4c5e351829
2 changed files with 123 additions and 66 deletions

View File

@ -113,32 +113,13 @@ impl TEdge for Edge {
area >= 0.0
}
fn intersect_segment(&self, segment: &Segment) -> ArrayVec<[f32; 3]> {
if segment.is_line() {
return self.intersect_line_segment(&segment.baseline);
}
let mut segment = *segment;
if segment.is_quadratic() {
segment = segment.to_cubic();
}
fn intersect_line_segment(&self, segment: &LineSegmentF32) -> ArrayVec<[f32; 3]> {
let mut results = ArrayVec::new();
let mut prev_t = 0.0;
while !results.is_full() {
if prev_t >= 1.0 {
break
}
let next_t = match self.intersect_cubic_segment(&segment, prev_t, 1.0) {
None => break,
Some(next_t) => next_t,
};
results.push(next_t);
prev_t = next_t + EPSILON;
let t = segment.intersection_t(&self.0);
if t >= 0.0 && t <= 1.0 {
results.push(t);
}
return results;
const EPSILON: f32 = 0.0001;
results
}
}
@ -166,15 +147,90 @@ impl Edge {
Edge(LineSegmentF32::new(&Point2DF32::from_euclid(rect.bottom_right()),
&Point2DF32::from_euclid(rect.bottom_left())))
}
}
#[derive(Clone, Copy, Debug)]
enum AxisAlignedEdge {
Left(f32),
Top(f32),
Right(f32),
Bottom(f32),
}
impl TEdge for AxisAlignedEdge {
#[inline]
fn point_is_inside(&self, point: &Point2DF32) -> bool {
match *self {
AxisAlignedEdge::Left(x) => point.x() >= x,
AxisAlignedEdge::Top(y) => point.y() >= y,
AxisAlignedEdge::Right(x) => point.x() <= x,
AxisAlignedEdge::Bottom(y) => point.y() <= y,
}
}
fn intersect_line_segment(&self, segment: &LineSegmentF32) -> ArrayVec<[f32; 3]> {
let mut results = ArrayVec::new();
let t = segment.intersection_t(&self.0);
let t = match *self {
AxisAlignedEdge::Left(x) | AxisAlignedEdge::Right(x) => segment.solve_t_for_x(x),
AxisAlignedEdge::Top(y) | AxisAlignedEdge::Bottom(y) => segment.solve_t_for_y(y),
};
if t >= 0.0 && t <= 1.0 {
results.push(t);
}
results
}
}
trait TEdge {
fn point_is_inside(&self, point: &Point2DF32) -> bool;
fn intersect_line_segment(&self, segment: &LineSegmentF32) -> 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 }
}
fn intersect_segment(&self, segment: &Segment) -> ArrayVec<[f32; 3]> {
if segment.is_line() {
return self.intersect_line_segment(&segment.baseline);
}
let mut segment = *segment;
if segment.is_quadratic() {
segment = segment.to_cubic();
}
let mut results = ArrayVec::new();
let mut prev_t = 0.0;
while !results.is_full() {
if prev_t >= 1.0 {
break
}
let next_t = match self.intersect_cubic_segment(&segment, prev_t, 1.0) {
None => break,
Some(next_t) => next_t,
};
results.push(next_t);
prev_t = next_t + EPSILON;
}
return results;
const EPSILON: f32 = 0.0001;
}
fn intersect_cubic_segment(&self, segment: &Segment, mut t_min: f32, mut t_max: f32)
-> Option<f32> {
@ -214,31 +270,6 @@ impl Edge {
}
}
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;
@ -305,6 +336,8 @@ trait ContourClipper where Self::Edge: TEdge {
}
}
// General convex polygon clipping in 2D
pub(crate) struct ContourPolygonClipper {
clip_polygon: SmallVec<[Point2DF32; 4]>,
contour: Contour,
@ -325,21 +358,8 @@ impl ContourPolygonClipper {
ContourPolygonClipper { clip_polygon: SmallVec::from_slice(clip_polygon), contour }
}
#[inline]
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()),
Point2DF32::from_euclid(clip_rect.bottom_left()),
], contour)
}
pub(crate) fn clip(mut self) -> Contour {
// TODO(pcwalton): Reenable this optimization.
/*if self.clip_rect.contains_rect(&self.contour.bounds()) {
return self.contour
}*/
// TODO(pcwalton): Maybe have a coarse circumscribed rect and use that for clipping?
let clip_polygon = mem::replace(&mut self.clip_polygon, SmallVec::default());
let mut prev = match clip_polygon.last() {
@ -361,6 +381,43 @@ enum EdgeRelativeLocation {
Outside,
}
// Fast axis-aligned box 2D clipping
pub(crate) struct ContourRectClipper {
clip_rect: Rect<f32>,
contour: Contour,
}
impl ContourClipper for ContourRectClipper {
type Edge = AxisAlignedEdge;
#[inline]
fn contour_mut(&mut self) -> &mut Contour {
&mut self.contour
}
}
impl ContourRectClipper {
#[inline]
pub(crate) fn new(clip_rect: &Rect<f32>, contour: Contour) -> ContourRectClipper {
ContourRectClipper { clip_rect: *clip_rect, contour }
}
pub(crate) fn clip(mut self) -> Contour {
// TODO(pcwalton): Reenable this optimization.
/*if self.clip_rect.contains_rect(&self.contour.bounds()) {
return self.contour
}*/
self.clip_against(AxisAlignedEdge::Left(self.clip_rect.origin.x));
self.clip_against(AxisAlignedEdge::Top(self.clip_rect.origin.y));
self.clip_against(AxisAlignedEdge::Right(self.clip_rect.max_x()));
self.clip_against(AxisAlignedEdge::Bottom(self.clip_rect.max_y()));
self.contour
}
}
// 3D quad clipping
pub struct PolygonClipper3D {

View File

@ -10,7 +10,7 @@
//! A compressed in-memory representation of paths.
use crate::clip::ContourPolygonClipper;
use crate::clip::{ContourPolygonClipper, ContourRectClipper};
use crate::line_segment::LineSegmentF32;
use crate::monotonic::MonotonicConversionIter;
use crate::point::Point2DF32;
@ -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 = ContourPolygonClipper::from_rect(clip_rect, contour).clip();
let contour = ContourRectClipper::new(clip_rect, contour).clip();
if !contour.is_empty() {
self.push_contour(contour);
}