Base the core arc primitive on a unit chord and transform.

This lets us handle ellipses better.
This commit is contained in:
Patrick Walton 2019-05-30 17:19:21 -07:00
parent 607a518544
commit 9756aa89f9
3 changed files with 27 additions and 34 deletions

View File

@ -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) {

View File

@ -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 {

View File

@ -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));
}
}
}