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.
This commit is contained in:
Patrick Walton 2019-05-29 20:22:03 -07:00
parent 9138e1e0bb
commit d5ba6ab6da
3 changed files with 33 additions and 26 deletions

View File

@ -248,6 +248,12 @@ impl LineSegmentF {
self.from() + self.vector().scale(t) self.from() + self.vector().scale(t)
} }
#[inline]
pub fn midpoint(&self) -> Point2DF {
self.sample(0.5)
}
#[inline] #[inline]
pub fn offset(&self, distance: f32) -> LineSegmentF { pub fn offset(&self, distance: f32) -> LineSegmentF {
if self.is_zero_length() { if self.is_zero_length() {

View File

@ -369,29 +369,31 @@ impl Contour {
} }
pub fn push_arc(&mut self, center: Point2DF, radius: f32, start_angle: f32, end_angle: f32) { 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 start = Point2DF::new(f32::cos(start_angle), f32::sin(start_angle));
let end = Point2DF::new(f32::cos(end_angle), f32::sin(end_angle)).scale(radius); let end = Point2DF::new(f32::cos(end_angle), f32::sin(end_angle));
let chord = LineSegmentF::new(start, end).translate(center); let chord = LineSegmentF::new(start, end).scale(radius).translate(center);
let full_circle = end_angle - start_angle >= PI * 2.0; 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] #[inline]
pub fn push_arc_from_chord(&mut self, center: Point2DF, chord: LineSegmentF) { pub fn push_arc_from_chord(&mut self, radius: f32, chord: LineSegmentF) {
self.push_arc_from_chord_full(center, chord, false); self.push_arc_from_chord_full(radius, chord, false);
} }
fn push_arc_from_chord_full(&mut self, fn push_arc_from_chord_full(&mut self,
center: Point2DF, radius: f32,
mut chord: LineSegmentF, mut chord: LineSegmentF,
full_circle: bool) { full_circle: bool) {
chord = chord.translate(-center); let chord_length = chord.vector().length();
let radius = chord.from().length(); let radius_minus_sagitta_sq = radius * radius - 0.25 * chord_length * chord_length;
chord = chord.scale(radius.recip()); let radius_minus_sagitta = f32::sqrt(f32::max(0.0, radius_minus_sagitta_sq));
let (mut vector, end_vector) = (UnitVector(chord.from()), UnitVector(chord.to())); 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); chord = chord.translate(-center).scale(radius.recip());
debug_assert!(f32::abs(end_vector.0.length() - 1.0) < EPSILON); let (mut vector, end_vector) = (UnitVector(chord.from()), UnitVector(chord.to()));
let scale = Transform2DF::from_scale(Point2DF::splat(radius)); let scale = Transform2DF::from_scale(Point2DF::splat(radius));
let translation = Transform2DF::from_translation(center); let translation = Transform2DF::from_translation(center);

View File

@ -100,8 +100,9 @@ impl<'a> OutlineStrokeToFill<'a> {
if closed && stroker.output.needs_join(self.style.line_join) { if closed && stroker.output.needs_join(self.style.line_join) {
let (p1, p0) = (stroker.output.position_of(1), stroker.output.position_of(0)); let (p1, p0) = (stroker.output.position_of(1), stroker.output.position_of(0));
let final_segment = LineSegmentF::new(p1, p0); let final_segment = LineSegmentF::new(p1, p0);
let join_point = stroker.input.position_of(0); stroker.output.add_join(self.style.line_width * 0.5,
stroker.output.add_join(self.style.line_join, join_point, &final_segment); self.style.line_join,
&final_segment);
} }
stroker.output.closed = true; stroker.output.closed = true;
@ -133,7 +134,7 @@ impl<'a> OutlineStrokeToFill<'a> {
LineCap::Round => { LineCap::Round => {
let offset = gradient.yx().scale_xy(Point2DF::new(-width, width)); let offset = gradient.yx().scale_xy(Point2DF::new(-width, width));
let chord = LineSegmentF::new(p1, p1 + offset); 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 { trait Offset {
fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour); 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 offset_once(&self, distance: f32) -> Self;
fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool; fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool;
} }
impl Offset for Segment { impl Offset for Segment {
fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour) { fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour) {
let join_point = self.baseline.from();
if self.baseline.square_length() < TOLERANCE * TOLERANCE { if self.baseline.square_length() < TOLERANCE * TOLERANCE {
self.add_to_contour(join, join_point, contour); self.add_to_contour(distance, join, contour);
return; return;
} }
let candidate = self.offset_once(distance); let candidate = self.offset_once(distance);
if self.error_is_within_tolerance(&candidate, 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; return;
} }
@ -207,7 +206,7 @@ impl Offset for Segment {
after.offset(distance, join, contour); 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. // Add join if necessary.
if contour.needs_join(join) { if contour.needs_join(join) {
let p3 = self.baseline.from(); let p3 = self.baseline.from();
@ -219,7 +218,7 @@ impl Offset for Segment {
self.ctrl.from() self.ctrl.from()
}; };
contour.add_join(join, join_point, &LineSegmentF::new(p4, p3)); contour.add_join(distance, join, &LineSegmentF::new(p4, p3));
} }
// Push segment. // Push segment.
@ -330,7 +329,7 @@ impl Contour {
(join == LineJoin::Miter || join == LineJoin::Round) && self.len() >= 2 (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 (p0, p1) = (self.position_of_last(2), self.position_of_last(1));
let prev_tangent = LineSegmentF::new(p0, p1); let prev_tangent = LineSegmentF::new(p0, p1);
@ -342,8 +341,8 @@ impl Contour {
} }
} }
LineJoin::Round => { LineJoin::Round => {
self.push_arc_from_chord(join_point, LineSegmentF::new(prev_tangent.to(), self.push_arc_from_chord(distance.abs(),
next_tangent.to())); LineSegmentF::new(prev_tangent.to(), next_tangent.to()));
} }
} }
} }