From d5ba6ab6da5da02ee16240c17e0d9ce5506f70d8 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 29 May 2019 20:22:03 -0700 Subject: [PATCH] Change the core arc primitive to chord and radius, not chord and center. This makes it harder to call the function with illegal arguments and simplifies the join/cap code. --- geometry/src/basic/line_segment.rs | 6 ++++++ geometry/src/outline.rs | 28 +++++++++++++++------------- geometry/src/stroke.rs | 25 ++++++++++++------------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/geometry/src/basic/line_segment.rs b/geometry/src/basic/line_segment.rs index 5c45f880..77de5648 100644 --- a/geometry/src/basic/line_segment.rs +++ b/geometry/src/basic/line_segment.rs @@ -248,6 +248,12 @@ impl LineSegmentF { self.from() + self.vector().scale(t) } + #[inline] + pub fn midpoint(&self) -> Point2DF { + self.sample(0.5) + } + + #[inline] pub fn offset(&self, distance: f32) -> LineSegmentF { if self.is_zero_length() { diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index ab668e7d..3b598e7c 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -369,29 +369,31 @@ impl Contour { } pub fn push_arc(&mut self, center: Point2DF, radius: f32, start_angle: f32, end_angle: f32) { - let start = Point2DF::new(f32::cos(start_angle), f32::sin(start_angle)).scale(radius); - let end = Point2DF::new(f32::cos(end_angle), f32::sin(end_angle)).scale(radius); - let chord = LineSegmentF::new(start, end).translate(center); + let start = Point2DF::new(f32::cos(start_angle), f32::sin(start_angle)); + let end = Point2DF::new(f32::cos(end_angle), f32::sin(end_angle)); + let chord = LineSegmentF::new(start, end).scale(radius).translate(center); let full_circle = end_angle - start_angle >= PI * 2.0; - self.push_arc_from_chord_full(center, chord, full_circle); + self.push_arc_from_chord_full(radius, chord, full_circle); } #[inline] - pub fn push_arc_from_chord(&mut self, center: Point2DF, chord: LineSegmentF) { - self.push_arc_from_chord_full(center, chord, false); + pub fn push_arc_from_chord(&mut self, radius: f32, chord: LineSegmentF) { + self.push_arc_from_chord_full(radius, chord, false); } fn push_arc_from_chord_full(&mut self, - center: Point2DF, + radius: f32, mut chord: LineSegmentF, full_circle: bool) { - chord = chord.translate(-center); - let radius = chord.from().length(); - chord = chord.scale(radius.recip()); - let (mut vector, end_vector) = (UnitVector(chord.from()), UnitVector(chord.to())); + let chord_length = chord.vector().length(); + let radius_minus_sagitta_sq = radius * radius - 0.25 * chord_length * chord_length; + let radius_minus_sagitta = f32::sqrt(f32::max(0.0, radius_minus_sagitta_sq)); + let scale_factor = radius_minus_sagitta / chord_length; + let center = chord.midpoint() + chord.vector().yx().scale_xy(Point2DF::new(-scale_factor, + scale_factor)); - debug_assert!(f32::abs(vector.0.length() - 1.0) < EPSILON); - debug_assert!(f32::abs(end_vector.0.length() - 1.0) < EPSILON); + chord = chord.translate(-center).scale(radius.recip()); + let (mut vector, end_vector) = (UnitVector(chord.from()), UnitVector(chord.to())); let scale = Transform2DF::from_scale(Point2DF::splat(radius)); let translation = Transform2DF::from_translation(center); diff --git a/geometry/src/stroke.rs b/geometry/src/stroke.rs index e910d04f..205080a5 100644 --- a/geometry/src/stroke.rs +++ b/geometry/src/stroke.rs @@ -100,8 +100,9 @@ impl<'a> OutlineStrokeToFill<'a> { if closed && stroker.output.needs_join(self.style.line_join) { let (p1, p0) = (stroker.output.position_of(1), stroker.output.position_of(0)); let final_segment = LineSegmentF::new(p1, p0); - let join_point = stroker.input.position_of(0); - stroker.output.add_join(self.style.line_join, join_point, &final_segment); + stroker.output.add_join(self.style.line_width * 0.5, + self.style.line_join, + &final_segment); } stroker.output.closed = true; @@ -133,7 +134,7 @@ impl<'a> OutlineStrokeToFill<'a> { LineCap::Round => { let offset = gradient.yx().scale_xy(Point2DF::new(-width, width)); let chord = LineSegmentF::new(p1, p1 + offset); - contour.push_arc_from_chord(p1 + offset.scale(0.5), chord); + contour.push_arc_from_chord(width * 0.5, chord); } } } @@ -179,23 +180,21 @@ impl<'a> ContourStrokeToFill<'a> { trait Offset { fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour); - fn add_to_contour(&self, join: LineJoin, join_point: Point2DF, contour: &mut Contour); + fn add_to_contour(&self, distance: f32, join: LineJoin, contour: &mut Contour); fn offset_once(&self, distance: f32) -> Self; fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool; } impl Offset for Segment { fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour) { - let join_point = self.baseline.from(); - if self.baseline.square_length() < TOLERANCE * TOLERANCE { - self.add_to_contour(join, join_point, contour); + self.add_to_contour(distance, join, contour); return; } let candidate = self.offset_once(distance); if self.error_is_within_tolerance(&candidate, distance) { - candidate.add_to_contour(join, join_point, contour); + candidate.add_to_contour(distance, join, contour); return; } @@ -207,7 +206,7 @@ impl Offset for Segment { after.offset(distance, join, contour); } - fn add_to_contour(&self, join: LineJoin, join_point: Point2DF, contour: &mut Contour) { + fn add_to_contour(&self, distance: f32, join: LineJoin, contour: &mut Contour) { // Add join if necessary. if contour.needs_join(join) { let p3 = self.baseline.from(); @@ -219,7 +218,7 @@ impl Offset for Segment { self.ctrl.from() }; - contour.add_join(join, join_point, &LineSegmentF::new(p4, p3)); + contour.add_join(distance, join, &LineSegmentF::new(p4, p3)); } // Push segment. @@ -330,7 +329,7 @@ impl Contour { (join == LineJoin::Miter || join == LineJoin::Round) && self.len() >= 2 } - fn add_join(&mut self, join: LineJoin, join_point: Point2DF, next_tangent: &LineSegmentF) { + fn add_join(&mut self, distance: f32, join: LineJoin, next_tangent: &LineSegmentF) { let (p0, p1) = (self.position_of_last(2), self.position_of_last(1)); let prev_tangent = LineSegmentF::new(p0, p1); @@ -342,8 +341,8 @@ impl Contour { } } LineJoin::Round => { - self.push_arc_from_chord(join_point, LineSegmentF::new(prev_tangent.to(), - next_tangent.to())); + self.push_arc_from_chord(distance.abs(), + LineSegmentF::new(prev_tangent.to(), next_tangent.to())); } } }