diff --git a/geometry/src/clip.rs b/geometry/src/clip.rs index 7172ce57..5e511ce6 100644 --- a/geometry/src/clip.rs +++ b/geometry/src/clip.rs @@ -11,6 +11,9 @@ use crate::line_segment::LineSegmentF32; use crate::outline::{Contour, PointFlags}; use crate::point::Point2DF32; +use crate::segment::Segment; +use crate::util::lerp; +use arrayvec::ArrayVec; use euclid::{Point2D, Rect, Vector3D}; use lyon_path::PathEvent; use std::mem; @@ -92,7 +95,7 @@ impl<'a> RectClipper<'a> { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] enum Edge { Left(f32), Top(f32), @@ -103,13 +106,31 @@ enum Edge { impl Edge { 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 trivially_test_segment(&self, segment: &Segment) -> EdgeRelativeLocation { + let from_inside = self.point_is_inside(&segment.baseline.from()); + 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 line_intersection(&self, line_segment: &LineSegmentF32) -> Point2DF32 { match *self { Edge::Left(x_edge) | Edge::Right(x_edge) => { @@ -120,6 +141,105 @@ impl Edge { } } } + + fn split_segment(&self, segment: &Segment) -> Option<(Segment, Segment)> { + if segment.is_line() { + return self.split_line_segment(segment); + } + + let mut segment = *segment; + if segment.is_quadratic() { + segment = segment.to_cubic(); + } + + self.intersect_cubic_segment(&segment, 0.0, 1.0).map(|t| { + self.fixup_clipped_segments(&segment.as_cubic_segment().split(t)) + }) + } + + fn split_line_segment(&self, segment: &Segment) -> Option<(Segment, Segment)> { + let intersection; + match *self { + Edge::Left(x_edge) | Edge::Right(x_edge) => { + if (segment.baseline.from_x() <= x_edge && segment.baseline.to_x() <= x_edge) || + (segment.baseline.from_x() >= x_edge && + segment.baseline.to_x() >= x_edge) { + return None + } + intersection = Point2DF32::new(x_edge, segment.baseline.solve_y_for_x(x_edge)); + } + Edge::Top(y_edge) | Edge::Bottom(y_edge) => { + if (segment.baseline.from_y() <= y_edge && segment.baseline.to_y() <= y_edge) || + (segment.baseline.from_y() >= y_edge && + segment.baseline.to_y() >= y_edge) { + return None + } + intersection = Point2DF32::new(segment.baseline.solve_x_for_y(y_edge), y_edge); + } + }; + Some((Segment::line(&LineSegmentF32::new(&segment.baseline.from(), &intersection)), + Segment::line(&LineSegmentF32::new(&intersection, &segment.baseline.to())))) + } + + fn intersect_cubic_segment(&self, segment: &Segment, t_min: f32, t_max: f32) -> Option { + /* + println!("... intersect_cubic_segment({:?}, {:?}, t=({}, {}))", + self, segment, t_min, t_max); + */ + let t_mid = lerp(t_min, t_max, 0.5); + if t_max - t_min < 0.001 { + return Some(t_mid); + } + + let (prev_segment, next_segment) = segment.as_cubic_segment().split(t_mid); + + let prev_cubic_segment = prev_segment.as_cubic_segment(); + let next_cubic_segment = next_segment.as_cubic_segment(); + + let (prev_min, prev_max, next_min, next_max, edge); + match *self { + Edge::Left(x) | Edge::Right(x) => { + prev_min = prev_cubic_segment.min_x(); + prev_max = prev_cubic_segment.max_x(); + next_min = next_cubic_segment.min_x(); + next_max = next_cubic_segment.max_x(); + edge = x; + } + Edge::Top(y) | Edge::Bottom(y) => { + prev_min = prev_cubic_segment.min_y(); + prev_max = prev_cubic_segment.max_y(); + next_min = next_cubic_segment.min_y(); + next_max = next_cubic_segment.max_y(); + edge = y; + } + } + + if prev_min < edge && edge < prev_max { + self.intersect_cubic_segment(segment, t_min, t_mid) + } else if next_min < edge && edge < next_max { + self.intersect_cubic_segment(segment, t_mid, t_max) + } else if (prev_max == edge && next_min == edge) || + (prev_min == edge && next_max == edge) { + Some(t_mid) + } else { + None + } + } + + fn fixup_clipped_segments(&self, segment: &(Segment, Segment)) -> (Segment, Segment) { + let (mut before, mut after) = *segment; + match *self { + Edge::Left(x) | Edge::Right(x) => { + before.baseline.set_to_x(x); + after.baseline.set_from_x(x); + } + Edge::Top(y) | Edge::Bottom(y) => { + before.baseline.set_to_y(y); + after.baseline.set_from_y(y); + } + } + (before, after) + } } pub(crate) struct ContourRectClipper { @@ -146,27 +266,78 @@ impl ContourRectClipper { } fn clip_against(&mut self, edge: Edge) { - let mut first_point = false; 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) { - if !edge.point_is_inside(&from) { - //println!("clip: {:?} {:?}", from, to); - let intersection = edge.line_intersection(&LineSegmentF32::new(&from, &to)); - add_line(&intersection, &mut self.contour, &mut first_point); + 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; } - add_line(&to, &mut self.contour, &mut first_point); - } else if edge.point_is_inside(&from) { - //println!("clip: {:?} {:?}", from, to); - let intersection = edge.line_intersection(&LineSegmentF32::new(&from, &to)); - add_line(&intersection, &mut self.contour, &mut first_point); + EdgeRelativeLocation::Intersecting => {} + } + + // We have a potential intersection. + //println!("potential intersection: {:?} edge: {:?}", segment, edge); + let mut starts_inside = edge.point_is_inside(&segment.baseline.from()); + while let Some((before_split, after_split)) = edge.split_segment(&segment) { + // Push the split segment if appropriate. + /* + println!("... ... before_split={:?} after_split={:?} starts_inside={:?}", + before_split, + after_split, + 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; + 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 add_line(to: &Point2DF32, output: &mut Contour, first_point: &mut bool) { - output.push_point(*to, PointFlags::empty()); - *first_point = false; + 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. + //check_point(&segment.baseline.from(), edge); + contour.push_point(segment.baseline.from(), PointFlags::empty()); + } + } + + //check_point(&segment.baseline.to(), edge); + contour.push_segment(*segment); } + + /* + fn check_point(point: &Point2DF32, edge: Edge) { + match edge { + Edge::Left(x) if point.x() + 0.1 >= x => return, + Edge::Top(y) if point.y() + 0.1 >= y => return, + Edge::Right(x) if point.x() - 0.1 <= x => return, + Edge::Bottom(y) if point.y() - 0.1 <= y => return, + _ => {} + } + panic!("point {:?} outside edge {:?}", point, edge); + } + */ } } + +enum EdgeRelativeLocation { + Intersecting, + Inside, + Outside, +} diff --git a/geometry/src/line_segment.rs b/geometry/src/line_segment.rs index 3b6eea7c..cec50b4f 100644 --- a/geometry/src/line_segment.rs +++ b/geometry/src/line_segment.rs @@ -66,6 +66,26 @@ impl LineSegmentF32 { self.0[3] } + #[inline] + pub fn set_from_x(&mut self, x: f32) { + self.0[0] = x + } + + #[inline] + pub fn set_from_y(&mut self, y: f32) { + self.0[1] = y + } + + #[inline] + pub fn set_to_x(&mut self, x: f32) { + self.0[2] = x + } + + #[inline] + pub fn set_to_y(&mut self, y: f32) { + self.0[3] = y + } + #[inline] pub fn scale(&self, factor: f32) -> LineSegmentF32 { LineSegmentF32(self.0 * F32x4::splat(factor)) @@ -176,6 +196,13 @@ impl LineSegmentF32 { self.reversed() } } + + // TODO(pcwalton): Optimize with SIMD. + #[inline] + pub fn square_length(&self) -> f32 { + let (dx, dy) = (self.to_x() - self.from_x(), self.to_y() - self.from_y()); + dx * dx + dy * dy + } } impl Sub for LineSegmentF32 { diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index 4671ca4e..d225e492 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -29,9 +29,9 @@ pub struct Outline { #[derive(Clone)] pub struct Contour { - points: Vec, - flags: Vec, - bounds: Rect, + pub(crate) points: Vec, + pub(crate) flags: Vec, + pub(crate) bounds: Rect, } bitflags! { @@ -164,6 +164,11 @@ impl Contour { self.points[index as usize] } + #[inline] + pub(crate) fn last_position(&self) -> Option { + self.points.last().cloned() + } + // TODO(pcwalton): SIMD. #[inline] pub(crate) fn push_point(&mut self, point: Point2DF32, flags: PointFlags) { diff --git a/geometry/src/segment.rs b/geometry/src/segment.rs index e648f82c..2c2708c0 100644 --- a/geometry/src/segment.rs +++ b/geometry/src/segment.rs @@ -239,6 +239,13 @@ impl<'s> CubicSegment<'s> { self.0.ctrl.from_y(), self.0.ctrl.to_y(), self.0.baseline.to_y()); + + // TODO(pcwalton): Optimize this. + if p0p1p2p3[0] <= p0p1p2p3[1] && p0p1p2p3[0] <= p0p1p2p3[2] && + p0p1p2p3[1] <= p0p1p2p3[3] && p0p1p2p3[2] <= p0p1p2p3[3] { + return (None, None); + } + let pxp0p1p2 = p0p1p2p3.wxyz(); let pxv0v1v2 = p0p1p2p3 - pxp0p1p2; let (v0, v1, v2) = (pxv0v1v2[1], pxv0v1v2[2], pxv0v1v2[3]); @@ -262,6 +269,15 @@ impl<'s> CubicSegment<'s> { const EPSILON: f32 = 0.001; } + + #[inline] + pub fn min_x(&self) -> f32 { f32::min(self.0.baseline.min_x(), self.0.ctrl.min_x()) } + #[inline] + pub fn min_y(&self) -> f32 { f32::min(self.0.baseline.min_y(), self.0.ctrl.min_y()) } + #[inline] + pub fn max_x(&self) -> f32 { f32::max(self.0.baseline.max_x(), self.0.ctrl.max_x()) } + #[inline] + pub fn max_y(&self) -> f32 { f32::max(self.0.baseline.max_y(), self.0.ctrl.max_y()) } } // Lyon interoperability diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index c1cc54fb..01e92119 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -197,13 +197,11 @@ impl BuiltObject { // TODO(pcwalton): Optimize this better with SIMD! pub fn generate_fill_primitives_for_line(&mut self, mut segment: LineSegmentF32, tile_y: i16) { - /* - println!("... generate_fill_primitives_for_line(): segment={:?} tile_y={} ({}-{})", + /*println!("... generate_fill_primitives_for_line(): segment={:?} tile_y={} ({}-{})", segment, tile_y, tile_y as f32 * TILE_HEIGHT as f32, - (tile_y + 1) as f32 * TILE_HEIGHT as f32); - */ + (tile_y + 1) as f32 * TILE_HEIGHT as f32);*/ let winding = segment.from_x() > segment.to_x(); let (segment_left, segment_right) = if !winding { diff --git a/renderer/src/tiles.rs b/renderer/src/tiles.rs index 4ef00b19..da7b50ea 100644 --- a/renderer/src/tiles.rs +++ b/renderer/src/tiles.rs @@ -449,6 +449,8 @@ impl ActiveEdge { tile_y: i16, ) -> Option { let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32; + /*println!("process_line_segment({:?}, tile_y={}) tile_bottom={}", + line_segment, tile_y, tile_bottom);*/ if line_segment.max_y() <= tile_bottom { built_object.generate_fill_primitives_for_line(*line_segment, tile_y); return None;