From 090e20676a2151a5e85197f33fe8a3bccedf304c Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 16 Jan 2019 20:04:29 -0800 Subject: [PATCH] Fix floating point error in clipping --- geometry/src/clip.rs | 66 ++++++++++++++++++------------------ geometry/src/line_segment.rs | 5 +++ 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/geometry/src/clip.rs b/geometry/src/clip.rs index 3461383e..7172ce57 100644 --- a/geometry/src/clip.rs +++ b/geometry/src/clip.rs @@ -8,6 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use crate::line_segment::LineSegmentF32; use crate::outline::{Contour, PointFlags}; use crate::point::Point2DF32; use euclid::{Point2D, Rect, Vector3D}; @@ -37,11 +38,12 @@ impl<'a> RectClipper<'a> { } fn clip_against(&self, edge: Edge, output: &mut Vec) { - let (mut from, mut path_start, mut first_point) = (Point2D::zero(), None, false); + let (mut from, mut path_start, mut first_point) = (Point2DF32::default(), None, false); let input = mem::replace(output, vec![]); for event in input { let to = match event { PathEvent::MoveTo(to) => { + let to = Point2DF32::from_euclid(to); path_start = Some(to); from = to; first_point = true; @@ -55,17 +57,19 @@ impl<'a> RectClipper<'a> { } PathEvent::LineTo(to) | PathEvent::QuadraticTo(_, to) | - PathEvent::CubicTo(_, _, to) => to, + PathEvent::CubicTo(_, _, to) => Point2DF32::from_euclid(to), PathEvent::Arc(..) => panic!("Arcs unsupported!"), }; if edge.point_is_inside(&to) { if !edge.point_is_inside(&from) { - add_line(&edge.line_intersection(&from, &to), output, &mut first_point); + let intersection = edge.line_intersection(&LineSegmentF32::new(&from, &to)); + add_line(&intersection, output, &mut first_point); } add_line(&to, output, &mut first_point); } else if edge.point_is_inside(&from) { - add_line(&edge.line_intersection(&from, &to), output, &mut first_point); + let intersection = edge.line_intersection(&LineSegmentF32::new(&from, &to)); + add_line(&intersection, output, &mut first_point); } from = to; @@ -76,12 +80,13 @@ impl<'a> RectClipper<'a> { } } - fn add_line(to: &Point2D, output: &mut Vec, first_point: &mut bool) { + fn add_line(to: &Point2DF32, output: &mut Vec, first_point: &mut bool) { + let to = to.as_euclid(); if *first_point { - output.push(PathEvent::MoveTo(*to)); + output.push(PathEvent::MoveTo(to)); *first_point = false; } else { - output.push(PathEvent::LineTo(*to)); + output.push(PathEvent::LineTo(to)); } } } @@ -96,25 +101,24 @@ enum Edge { } impl Edge { - fn point_is_inside(&self, point: &Point2D) -> bool { + fn point_is_inside(&self, point: &Point2DF32) -> bool { match *self { - Edge::Left(x_edge) => point.x >= x_edge, - Edge::Top(y_edge) => point.y >= y_edge, - Edge::Right(x_edge) => point.x <= x_edge, - Edge::Bottom(y_edge) => point.y <= y_edge, + Edge::Left(x_edge) => point.x() >= x_edge, + Edge::Top(y_edge) => point.y() >= y_edge, + Edge::Right(x_edge) => point.x() <= x_edge, + Edge::Bottom(y_edge) => point.y() <= y_edge, } } - fn line_intersection(&self, start_point: &Point2D, endpoint: &Point2D) - -> Point2D { - let start_point = Vector3D::new(start_point.x, start_point.y, 1.0); - let endpoint = Vector3D::new(endpoint.x, endpoint.y, 1.0); - let edge = match *self { - Edge::Left(x_edge) | Edge::Right(x_edge) => Vector3D::new(1.0, 0.0, -x_edge), - Edge::Top(y_edge) | Edge::Bottom(y_edge) => Vector3D::new(0.0, 1.0, -y_edge), - }; - let intersection = start_point.cross(endpoint).cross(edge); - Point2D::new(intersection.x / intersection.z, intersection.y / intersection.z) + fn line_intersection(&self, line_segment: &LineSegmentF32) -> Point2DF32 { + match *self { + Edge::Left(x_edge) | Edge::Right(x_edge) => { + Point2DF32::new(x_edge, line_segment.solve_y_for_x(x_edge)) + } + Edge::Top(y_edge) | Edge::Bottom(y_edge) => { + Point2DF32::new(line_segment.solve_x_for_y(y_edge), y_edge) + } + } } } @@ -146,21 +150,17 @@ impl ContourRectClipper { let input = mem::replace(&mut self.contour, Contour::new()); for event in input.iter() { let (from, to) = (event.baseline.from(), event.baseline.to()); - if edge.point_is_inside(&to.as_euclid()) { - if !edge.point_is_inside(&from.as_euclid()) { + if edge.point_is_inside(&to) { + if !edge.point_is_inside(&from) { //println!("clip: {:?} {:?}", from, to); - add_line(&Point2DF32::from_euclid(edge.line_intersection(&from.as_euclid(), - &to.as_euclid())), - &mut self.contour, - &mut first_point); + let intersection = edge.line_intersection(&LineSegmentF32::new(&from, &to)); + add_line(&intersection, &mut self.contour, &mut first_point); } add_line(&to, &mut self.contour, &mut first_point); - } else if edge.point_is_inside(&from.as_euclid()) { + } else if edge.point_is_inside(&from) { //println!("clip: {:?} {:?}", from, to); - add_line(&Point2DF32::from_euclid(edge.line_intersection(&from.as_euclid(), - &to.as_euclid())), - &mut self.contour, - &mut first_point); + let intersection = edge.line_intersection(&LineSegmentF32::new(&from, &to)); + add_line(&intersection, &mut self.contour, &mut first_point); } } diff --git a/geometry/src/line_segment.rs b/geometry/src/line_segment.rs index 44bb9b62..3b6eea7c 100644 --- a/geometry/src/line_segment.rs +++ b/geometry/src/line_segment.rs @@ -113,6 +113,11 @@ impl LineSegmentF32 { (y - self.from_y()) / (self.to_y() - self.from_y()) } + #[inline] + pub fn solve_x_for_y(&self, y: f32) -> f32 { + util::lerp(self.from_x(), self.to_x(), self.solve_t_for_y(y)) + } + #[inline] pub fn solve_y_for_x(&self, x: f32) -> f32 { util::lerp(self.from_y(), self.to_y(), self.solve_t_for_x(x))