From d75ed71af44ca6bb7f38d7fe7be8f60e6b8fc32f Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 20 Feb 2019 12:41:43 -0800 Subject: [PATCH] WIP before moving to a segment basis --- geometry/src/basic/line_segment.rs | 32 ++++++-- geometry/src/clip.rs | 7 +- geometry/src/outline.rs | 13 +++- geometry/src/stroke.rs | 113 +++++++++++++++++++++++++++++ svg/src/lib.rs | 10 ++- 5 files changed, 161 insertions(+), 14 deletions(-) diff --git a/geometry/src/basic/line_segment.rs b/geometry/src/basic/line_segment.rs index 04e8cf89..fed9ee88 100644 --- a/geometry/src/basic/line_segment.rs +++ b/geometry/src/basic/line_segment.rs @@ -13,7 +13,7 @@ use crate::basic::point::Point2DF32; use crate::util; use pathfinder_simd::default::F32x4; -use std::ops::Sub; +use std::ops::{Add, Sub}; #[derive(Clone, Copy, Debug, PartialEq, Default)] pub struct LineSegmentF32(pub F32x4); @@ -224,20 +224,42 @@ impl LineSegmentF32 { } // http://www.cs.swan.ac.uk/~cssimon/line_intersection.html - pub fn intersection_t(&self, other: &LineSegmentF32) -> f32 { + pub fn intersection_t(&self, other: &LineSegmentF32) -> Option { let d0d1 = self.vector().0.concat_xy_xy(other.vector().0); let offset = other.from() - self.from(); let factors = d0d1.concat_wz_yx(offset.0); let terms = d0d1 * factors; - let t = (terms[3] - terms[2]) / (terms[0] - terms[1]); - //println!("intersection_t({:?}, {:?})={} (d0d1={:?}, factors={:?})", self, other, t, d0d1, factors); - t + let denom = terms[0] - terms[1]; + if f32::abs(denom) < EPSILON { + return None; + } + return Some((terms[3] - terms[2]) / denom); + + const EPSILON: f32 = 0.0001; } #[inline] pub fn sample(&self, t: f32) -> Point2DF32 { self.from() + self.vector().scale(t) } + + #[inline] + pub fn offset(&self, amount: f32) -> LineSegmentF32 { + *self + self.vector().yx().normalize().scale_xy(Point2DF32::new(-amount, amount)) + } + + #[inline] + pub fn is_zero_length(&self) -> bool { + self.vector().is_zero() + } +} + +impl Add for LineSegmentF32 { + type Output = LineSegmentF32; + #[inline] + fn add(self, point: Point2DF32) -> LineSegmentF32 { + LineSegmentF32(self.0 + point.0.xyxy()) + } } impl Sub for LineSegmentF32 { diff --git a/geometry/src/clip.rs b/geometry/src/clip.rs index 013ee1a3..d54cb62a 100644 --- a/geometry/src/clip.rs +++ b/geometry/src/clip.rs @@ -112,9 +112,10 @@ impl TEdge for Edge { fn intersect_line_segment(&self, segment: &LineSegmentF32) -> ArrayVec<[f32; 3]> { let mut results = ArrayVec::new(); - let t = segment.intersection_t(&self.0); - if t >= 0.0 && t <= 1.0 { - results.push(t); + if let Some(t) = segment.intersection_t(&self.0) { + if t >= 0.0 && t <= 1.0 { + results.push(t); + } } results } diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index 41caee30..17870cef 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -25,7 +25,7 @@ use std::mem; #[derive(Clone)] pub struct Outline { pub contours: Vec, - bounds: RectF32, + pub(crate) bounds: RectF32, } #[derive(Clone)] @@ -304,10 +304,17 @@ impl Contour { segment } + #[inline] + pub fn hull_segment_after(&self, prev_point_index: u32) -> LineSegmentF32 { + let next_point_index = self.next_point_index_of(prev_point_index); + LineSegmentF32::new(&self.points[prev_point_index as usize], + &self.points[next_point_index as usize]) + } + #[inline] pub fn point_is_endpoint(&self, point_index: u32) -> bool { !self.flags[point_index as usize] - .intersects(PointFlags::CONTROL_POINT_0 | PointFlags::CONTROL_POINT_1) + .intersects(PointFlags::CONTROL_POINT_0 | PointFlags::CONTROL_POINT_1) } #[inline] @@ -510,7 +517,7 @@ impl Contour { true } - fn update_bounds(&self, bounds: &mut Option) { + pub(crate) fn update_bounds(&self, bounds: &mut Option) { *bounds = Some(match *bounds { None => self.bounds, Some(bounds) => bounds.union_rect(self.bounds), diff --git a/geometry/src/stroke.rs b/geometry/src/stroke.rs index 08e4e702..8e47bf45 100644 --- a/geometry/src/stroke.rs +++ b/geometry/src/stroke.rs @@ -10,9 +10,13 @@ //! Utilities for converting path strokes to fills. +use crate::basic::line_segment::LineSegmentF32; +use crate::basic::rect::RectF32; +use crate::outline::{Contour, Outline}; use crate::segments::{Segment, SegmentIter}; use lyon_path::PathEvent; use lyon_path::iterator::PathIterator; +use std::mem; #[derive(Clone, Copy, Debug)] pub struct StrokeStyle { @@ -144,3 +148,112 @@ enum StrokeToFillState { Forward, Backward, } + +// Pathfinder 3 + +pub struct OutlineStrokeToFill { + pub outline: Outline, + pub radius: f32, +} + +impl OutlineStrokeToFill { + #[inline] + pub fn new(outline: Outline, radius: f32) -> OutlineStrokeToFill { + OutlineStrokeToFill { outline, radius } + } + + #[inline] + pub fn offset(&mut self) { + let mut new_bounds = None; + for contour in &mut self.outline.contours { + let input = mem::replace(contour, Contour::new()); + let mut contour_stroke_to_fill = + ContourStrokeToFill::new(input, Contour::new(), self.radius); + contour_stroke_to_fill.offset_forward(); + contour_stroke_to_fill.offset_backward(); + *contour = contour_stroke_to_fill.output; + contour.update_bounds(&mut new_bounds); + } + + self.outline.bounds = new_bounds.unwrap_or_else(|| RectF32::default()); + } +} + +struct ContourStrokeToFill { + input: Contour, + output: Contour, + radius: f32, +} + +impl ContourStrokeToFill { + #[inline] + fn new(input: Contour, output: Contour, radius: f32) -> ContourStrokeToFill { + ContourStrokeToFill { input, output, radius } + } + + fn offset_forward(&mut self) { + for point_index in 0..(self.input.points.len() as u32) { + let mut prev_point_index = self.input.prev_point_index_of(point_index); + while prev_point_index != point_index && + self.input.position_of(prev_point_index) == + self.input.position_of(point_index) { + prev_point_index = self.input.prev_point_index_of(prev_point_index); + } + + let mut next_point_index = self.input.next_point_index_of(point_index); + while next_point_index != point_index && + self.input.position_of(next_point_index) == + self.input.position_of(point_index) { + next_point_index = self.input.next_point_index_of(next_point_index); + } + + let prev_line_segment = LineSegmentF32::new(&self.input.position_of(prev_point_index), + &self.input.position_of(point_index)); + let next_line_segment = LineSegmentF32::new(&self.input.position_of(point_index), + &self.input.position_of(next_point_index)); + let prev_offset_line_segment = prev_line_segment.offset(self.radius); + let next_offset_line_segment = next_line_segment.offset(self.radius); + + let new_position; + match prev_offset_line_segment.intersection_t(&next_offset_line_segment) { + None => new_position = self.input.position_of(point_index), + Some(t) => new_position = prev_offset_line_segment.sample(t), + } + + self.output.push_point(new_position, self.input.flags[point_index as usize], true); + } + } + + fn offset_backward(&mut self) { + for point_index in (0..(self.input.points.len() as u32)).rev() { + let mut prev_point_index = self.input.prev_point_index_of(point_index); + while prev_point_index != point_index && + self.input.position_of(prev_point_index) == + self.input.position_of(point_index) { + prev_point_index = self.input.prev_point_index_of(prev_point_index); + } + + let mut next_point_index = self.input.next_point_index_of(point_index); + while next_point_index != point_index && + self.input.position_of(next_point_index) == + self.input.position_of(point_index) { + next_point_index = self.input.next_point_index_of(next_point_index); + } + + let prev_line_segment = LineSegmentF32::new(&self.input.position_of(prev_point_index), + &self.input.position_of(point_index)); + let next_line_segment = LineSegmentF32::new(&self.input.position_of(point_index), + &self.input.position_of(next_point_index)); + let prev_offset_line_segment = prev_line_segment.offset(-self.radius); + let next_offset_line_segment = next_line_segment.offset(-self.radius); + + let new_position; + match prev_offset_line_segment.intersection_t(&next_offset_line_segment) { + None => new_position = self.input.position_of(point_index), + Some(t) => new_position = prev_offset_line_segment.sample(t), + } + + self.output.push_point(new_position, self.input.flags[point_index as usize], true); + } + } +} diff --git a/svg/src/lib.rs b/svg/src/lib.rs index 6e4a3910..3081bc9e 100644 --- a/svg/src/lib.rs +++ b/svg/src/lib.rs @@ -18,7 +18,7 @@ use pathfinder_geometry::basic::transform2d::{Transform2DF32, Transform2DF32Path use pathfinder_geometry::outline::Outline; use pathfinder_geometry::segment::{PathEventsToSegments, Segment}; use pathfinder_geometry::segment::{SegmentFlags, SegmentsToPathEvents}; -use pathfinder_geometry::stroke::{StrokeStyle, StrokeToFillIter}; +use pathfinder_geometry::stroke::OutlineStrokeToFill; use pathfinder_renderer::paint::{ColorU, Paint}; use pathfinder_renderer::scene::{PathObject, PathObjectKind, Scene}; use std::mem; @@ -91,13 +91,17 @@ fn process_node(scene: &mut Scene, node: &Node, transform: &Transform2DF32) { f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH); let path = UsvgPathToSegments::new(path.segments.iter().cloned()); - let path = SegmentsToPathEvents::new(path); + /*let path = SegmentsToPathEvents::new(path); let path = PathIter::new(path); let path = StrokeToFillIter::new(path, StrokeStyle::new(stroke_width)); - let path = PathEventsToSegments::new(path); + let path = PathEventsToSegments::new(path);*/ let path = Transform2DF32PathIter::new(path, &transform); let outline = Outline::from_segments(path); + let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_width); + stroke_to_fill.offset(); + let outline = stroke_to_fill.outline; + scene.bounds = scene.bounds.union_rect(outline.bounds()); scene.objects.push(PathObject::new( outline,