diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 4a80e0e7..e5a448a2 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -311,7 +311,9 @@ impl Path2D { #[inline] pub fn arc(&mut self, center: Point2DF, radius: f32, start_angle: f32, end_angle: f32) { - self.current_contour.push_arc(center, radius, start_angle, end_angle); + let mut transform = Transform2DF::from_scale(Point2DF::splat(radius)); + transform = transform.post_mul(&Transform2DF::from_translation(center)); + self.current_contour.push_arc(&transform, start_angle, end_angle); } pub fn rect(&mut self, rect: RectF) { diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index 27540cd2..f46b2be4 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -368,34 +368,20 @@ impl Contour { self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds); } - pub fn push_arc(&mut self, center: Point2DF, radius: f32, start_angle: f32, end_angle: f32) { + pub fn push_arc(&mut self, transform: &Transform2DF, start_angle: f32, end_angle: f32) { if end_angle - start_angle >= PI * 2.0 { - return self.push_circle(center, radius); + self.push_ellipse(transform); + } else { + let start = Point2DF::new(f32::cos(start_angle), f32::sin(start_angle)); + let end = Point2DF::new(f32::cos(end_angle), f32::sin(end_angle)); + self.push_arc_from_unit_chord(transform, LineSegmentF::new(start, end)); } - - 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); - self.push_arc_from_chord(radius, chord); } - #[inline] - pub fn push_arc_from_chord(&mut self, radius: f32, mut chord: LineSegmentF) { - 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)); - - chord = chord.translate(-center).scale(radius.recip()); + pub fn push_arc_from_unit_chord(&mut self, transform: &Transform2DF, chord: LineSegmentF) { 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); - let mut first_segment = true; + loop { let mut sweep_vector = end_vector.rev_rotate_by(vector); let last = sweep_vector.0.x() >= -EPSILON && sweep_vector.0.y() >= -EPSILON; @@ -407,7 +393,7 @@ impl Contour { let mut segment = Segment::arc_from_cos(sweep_vector.0.x()); let rotation = Transform2DF::from_rotation_vector(sweep_vector.halve_angle().rotate_by(vector)); - segment = segment.transform(&scale.post_mul(&rotation).post_mul(&translation)); + segment = segment.transform(&rotation.post_mul(&transform)); if first_segment { self.push_full_segment(&segment, true); @@ -426,10 +412,7 @@ impl Contour { const EPSILON: f32 = 0.001; } - fn push_circle(&mut self, center: Point2DF, radius: f32) { - let scale = Transform2DF::from_scale(Point2DF::splat(radius)); - let translation = Transform2DF::from_translation(center); - + pub fn push_ellipse(&mut self, transform: &Transform2DF) { let rotations = [ Transform2DF::default(), Transform2DF::from_rotation_vector(UnitVector(Point2DF::new( 0.0, 1.0))), @@ -440,7 +423,7 @@ impl Contour { let base_segment = Segment::arc_from_cos(0.0); for (rotation_index, rotation) in rotations.iter().enumerate() { - let segment = base_segment.transform(&scale.post_mul(rotation).post_mul(&translation)); + let segment = base_segment.transform(&rotation.post_mul(&transform)); if rotation_index == 0 { self.push_full_segment(&segment, true); } else { diff --git a/geometry/src/stroke.rs b/geometry/src/stroke.rs index 128c549b..fe70d67d 100644 --- a/geometry/src/stroke.rs +++ b/geometry/src/stroke.rs @@ -13,6 +13,7 @@ use crate::basic::line_segment::LineSegmentF; use crate::basic::point::Point2DF; use crate::basic::rect::RectF; +use crate::basic::transform2d::Transform2DF; use crate::outline::{Contour, Outline}; use crate::segment::Segment; use std::f32; @@ -133,9 +134,12 @@ impl<'a> OutlineStrokeToFill<'a> { contour.push_endpoint(p4); } LineCap::Round => { - let offset = gradient.yx().scale_xy(Point2DF::new(-width, width)); - let chord = LineSegmentF::new(p1, p1 + offset); - contour.push_arc_from_chord(width * 0.5, chord); + let scale = Point2DF::splat(width * 0.5); + let offset = gradient.yx().scale_xy(Point2DF::new(-1.0, 1.0)); + let mut transform = Transform2DF::from_scale(scale); + let translation = p1 + offset.scale(width * 0.5); + transform = transform.post_mul(&Transform2DF::from_translation(translation)); + contour.push_arc_from_unit_chord(&transform, LineSegmentF::new(-offset, offset)); } } } @@ -365,8 +369,12 @@ impl Contour { } } LineJoin::Round => { - self.push_arc_from_chord(distance.abs(), - LineSegmentF::new(prev_tangent.to(), next_tangent.to())); + let scale = Point2DF::splat(distance.abs()); + let mut transform = Transform2DF::from_scale(scale); + transform = transform.post_mul(&Transform2DF::from_translation(join_point)); + let chord_from = (prev_tangent.to() - join_point).normalize(); + let chord_to = (next_tangent.to() - join_point).normalize(); + self.push_arc_from_unit_chord(&transform, LineSegmentF::new(chord_from, chord_to)); } } }