From b9e3952246ca14a81792c823c726324a08528271 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 20 Feb 2019 16:21:58 -0800 Subject: [PATCH] Reimplement segment offsetting --- geometry/src/basic/line_segment.rs | 8 +- geometry/src/basic/point.rs | 5 + geometry/src/outline.rs | 16 ++++ geometry/src/stroke.rs | 143 +++++++++++++++++------------ 4 files changed, 112 insertions(+), 60 deletions(-) diff --git a/geometry/src/basic/line_segment.rs b/geometry/src/basic/line_segment.rs index fed9ee88..315c3187 100644 --- a/geometry/src/basic/line_segment.rs +++ b/geometry/src/basic/line_segment.rs @@ -244,8 +244,12 @@ impl LineSegmentF32 { } #[inline] - pub fn offset(&self, amount: f32) -> LineSegmentF32 { - *self + self.vector().yx().normalize().scale_xy(Point2DF32::new(-amount, amount)) + pub fn offset(&self, distance: f32) -> LineSegmentF32 { + if self.is_zero_length() { + *self + } else { + *self + self.vector().yx().normalize().scale_xy(Point2DF32::new(-distance, distance)) + } } #[inline] diff --git a/geometry/src/basic/point.rs b/geometry/src/basic/point.rs index 2c5ccfe4..9a742d62 100644 --- a/geometry/src/basic/point.rs +++ b/geometry/src/basic/point.rs @@ -133,6 +133,11 @@ impl Point2DF32 { pub fn is_zero(&self) -> bool { *self == Point2DF32::default() } + + #[inline] + pub fn lerp(&self, other: Point2DF32, t: f32) -> Point2DF32 { + *self + (other - *self).scale(t) + } } impl PartialEq for Point2DF32 { diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index 17870cef..67de216d 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -245,6 +245,8 @@ impl Contour { point: Point2DF32, flags: PointFlags, update_bounds: bool) { + debug_assert!(!point.x().is_nan() && !point.y().is_nan()); + if update_bounds { let first = self.is_empty(); union_rect(&mut self.bounds, point, first); @@ -274,6 +276,20 @@ impl Contour { self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds); } + #[inline] + pub(crate) fn push_full_segment(&mut self, segment: &Segment, update_bounds: bool) { + self.push_point(segment.baseline.from(), PointFlags::empty(), update_bounds); + + if !segment.is_line() { + self.push_point(segment.ctrl.from(), PointFlags::CONTROL_POINT_0, update_bounds); + if !segment.is_quadratic() { + self.push_point(segment.ctrl.to(), PointFlags::CONTROL_POINT_1, update_bounds); + } + } + + self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds); + } + #[inline] pub fn segment_after(&self, point_index: u32) -> Segment { debug_assert!(self.point_is_endpoint(point_index)); diff --git a/geometry/src/stroke.rs b/geometry/src/stroke.rs index 8e47bf45..fad31702 100644 --- a/geometry/src/stroke.rs +++ b/geometry/src/stroke.rs @@ -13,6 +13,7 @@ use crate::basic::line_segment::LineSegmentF32; use crate::basic::rect::RectF32; use crate::outline::{Contour, Outline}; +use crate::segment::Segment as SegmentPF3; use crate::segments::{Segment, SegmentIter}; use lyon_path::PathEvent; use lyon_path::iterator::PathIterator; @@ -192,68 +193,94 @@ impl ContourStrokeToFill { } 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); + for segment in self.input.iter() { + segment.offset(self.radius, &mut self.output); } } 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); + // FIXME(pcwalton) + let mut segments: Vec<_> = self.input.iter().map(|segment| segment.reversed()).collect(); + segments.reverse(); + for segment in &segments { + segment.offset(self.radius, &mut self.output); } } } + +trait Offset { + fn offset(&self, distance: f32, contour: &mut Contour); +} + +impl Offset for SegmentPF3 { + fn offset(&self, distance: f32, contour: &mut Contour) { + if self.is_line() { + contour.push_full_segment(&SegmentPF3::line(&self.baseline.offset(distance)), true); + return; + } + + if self.is_quadratic() { + let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.from()); + let mut segment_1 = LineSegmentF32::new(&self.ctrl.from(), &self.baseline.to()); + segment_0 = segment_0.offset(distance); + segment_1 = segment_1.offset(distance); + let ctrl = match segment_0.intersection_t(&segment_1) { + Some(t) => segment_0.sample(t), + None => segment_0.to().lerp(segment_1.from(), 0.5), + }; + let baseline = LineSegmentF32::new(&segment_0.from(), &segment_1.to()); + contour.push_full_segment(&SegmentPF3::quadratic(&baseline, &ctrl), true); + return; + } + + debug_assert!(self.is_cubic()); + + if self.baseline.from() == self.ctrl.from() { + let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.to()); + let mut segment_1 = LineSegmentF32::new(&self.ctrl.to(), &self.baseline.to()); + segment_0 = segment_0.offset(distance); + segment_1 = segment_1.offset(distance); + let ctrl = match segment_0.intersection_t(&segment_1) { + Some(t) => segment_0.sample(t), + None => segment_0.to().lerp(segment_1.from(), 0.5), + }; + let baseline = LineSegmentF32::new(&segment_0.from(), &segment_1.to()); + let ctrl = LineSegmentF32::new(&segment_0.from(), &ctrl); + contour.push_full_segment(&SegmentPF3::cubic(&baseline, &ctrl), true); + return; + } + + if self.ctrl.to() == self.baseline.to() { + let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.from()); + let mut segment_1 = LineSegmentF32::new(&self.ctrl.from(), &self.baseline.to()); + segment_0 = segment_0.offset(distance); + segment_1 = segment_1.offset(distance); + let ctrl = match segment_0.intersection_t(&segment_1) { + Some(t) => segment_0.sample(t), + None => segment_0.to().lerp(segment_1.from(), 0.5), + }; + let baseline = LineSegmentF32::new(&segment_0.from(), &segment_1.to()); + let ctrl = LineSegmentF32::new(&ctrl, &segment_1.to()); + contour.push_full_segment(&SegmentPF3::cubic(&baseline, &ctrl), true); + return; + } + + let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.from()); + let mut segment_1 = LineSegmentF32::new(&self.ctrl.from(), &self.ctrl.to()); + let mut segment_2 = LineSegmentF32::new(&self.ctrl.to(), &self.baseline.to()); + segment_0 = segment_0.offset(distance); + segment_1 = segment_1.offset(distance); + segment_2 = segment_2.offset(distance); + let (ctrl_0, ctrl_1) = match (segment_0.intersection_t(&segment_1), + segment_1.intersection_t(&segment_2)) { + (Some(t0), Some(t1)) => (segment_0.sample(t0), segment_1.sample(t1)), + _ => { + (segment_0.to().lerp(segment_1.from(), 0.5), + segment_1.to().lerp(segment_2.from(), 0.5)) + } + }; + let baseline = LineSegmentF32::new(&segment_0.from(), &segment_2.to()); + let ctrl = LineSegmentF32::new(&ctrl_0, &ctrl_1); + contour.push_full_segment(&SegmentPF3::cubic(&baseline, &ctrl), true); + } +}