diff --git a/geometry/src/basic/line_segment.rs b/geometry/src/basic/line_segment.rs index 88c60c17..d6a900b1 100644 --- a/geometry/src/basic/line_segment.rs +++ b/geometry/src/basic/line_segment.rs @@ -87,6 +87,11 @@ impl LineSegmentF32 { self.0[3] = y } + #[inline] + pub fn translate(&self, offset: Point2DF32) -> LineSegmentF32 { + LineSegmentF32(self.0 + offset.0.xyxy()) + } + #[inline] pub fn scale(&self, factor: f32) -> LineSegmentF32 { LineSegmentF32(self.0 * F32x4::splat(factor)) diff --git a/geometry/src/basic/transform2d.rs b/geometry/src/basic/transform2d.rs index 72430551..e4c36b21 100644 --- a/geometry/src/basic/transform2d.rs +++ b/geometry/src/basic/transform2d.rs @@ -15,6 +15,7 @@ use crate::basic::point::Point2DF32; use crate::basic::rect::RectF32; use crate::basic::transform3d::Transform3DF32; use crate::segment::Segment; +use crate::unit_vector::UnitVector; use pathfinder_simd::default::F32x4; use std::ops::Sub; @@ -37,8 +38,12 @@ impl Matrix2x2F32 { #[inline] pub fn from_rotation(theta: f32) -> Matrix2x2F32 { - let (sin_theta, cos_theta) = (theta.sin(), theta.cos()); - Matrix2x2F32(F32x4::new(cos_theta, sin_theta, -sin_theta, cos_theta)) + Matrix2x2F32::from_rotation_vector(UnitVector::from_angle(theta)) + } + + #[inline] + pub fn from_rotation_vector(vector: UnitVector) -> Matrix2x2F32 { + Matrix2x2F32((vector.0).0.xyyx() * F32x4::new(1.0, 1.0, -1.0, 1.0)) } #[inline] @@ -140,6 +145,14 @@ impl Transform2DF32 { } } + #[inline] + pub fn from_rotation_vector(vector: UnitVector) -> Transform2DF32 { + Transform2DF32 { + matrix: Matrix2x2F32::from_rotation_vector(vector), + vector: Point2DF32::default(), + } + } + #[inline] pub fn from_translation(vector: Point2DF32) -> Transform2DF32 { Transform2DF32 { matrix: Matrix2x2F32::default(), vector } diff --git a/geometry/src/lib.rs b/geometry/src/lib.rs index 9019d760..9379e471 100644 --- a/geometry/src/lib.rs +++ b/geometry/src/lib.rs @@ -27,3 +27,4 @@ pub mod stroke; pub mod util; mod dilation; +mod unit_vector; diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index 7c9a5eb1..d739541b 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -19,12 +19,11 @@ use crate::clip::{self, ContourPolygonClipper, ContourRectClipper}; use crate::dilation::ContourDilator; use crate::orientation::Orientation; use crate::segment::{Segment, SegmentFlags, SegmentKind}; -use std::f32::consts::{FRAC_PI_2, PI}; +use crate::unit_vector::UnitVector; +use std::f32::consts::PI; use std::fmt::{self, Debug, Formatter}; use std::mem; -const TWO_PI: f32 = PI * 2.0; - #[derive(Clone)] pub struct Outline { pub(crate) contours: Vec, @@ -369,33 +368,44 @@ impl Contour { self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds); } - pub fn push_arc(&mut self, - center: Point2DF32, - radius: f32, - mut start_angle: f32, - mut end_angle: f32) { - start_angle %= TWO_PI; - end_angle %= TWO_PI; - if end_angle <= start_angle { - end_angle += TWO_PI; - } + pub fn push_arc(&mut self, center: Point2DF32, radius: f32, start_angle: f32, end_angle: f32) { + let start = Point2DF32::new(f32::cos(start_angle), f32::sin(start_angle)).scale(radius); + let end = Point2DF32::new(f32::cos(end_angle), f32::sin(end_angle)).scale(radius); + let chord = LineSegmentF32::new(start, end).translate(center); + let full_circle = end_angle - start_angle >= PI * 2.0; + self.push_arc_from_chord_full(center, chord, full_circle); + } + + #[inline] + pub fn push_arc_from_chord(&mut self, center: Point2DF32, chord: LineSegmentF32) { + self.push_arc_from_chord_full(center, chord, false); + } + + fn push_arc_from_chord_full(&mut self, + center: Point2DF32, + mut chord: LineSegmentF32, + 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 scale = Transform2DF32::from_scale(Point2DF32::splat(radius)); let translation = Transform2DF32::from_translation(center); - let (mut angle, mut first_segment) = (start_angle, true); - while angle < end_angle { - let sweep_angle = f32::min(FRAC_PI_2, end_angle - angle); - let mut segment = Segment::arc(sweep_angle); - let rotation = Transform2DF32::from_rotation(sweep_angle * 0.5 + angle); - segment = segment.transform(&scale.post_mul(&rotation).post_mul(&translation)); + let mut first_segment = true; + loop { + let mut sweep_vector = end_vector.rev_rotate_by(vector); + let last = !(full_circle && first_segment) && + sweep_vector.0.x() >= -EPSILON && sweep_vector.0.y() >= -EPSILON; + if !last { + sweep_vector = UnitVector(Point2DF32::new(0.0, 1.0)); + } - debug!("angle={} start_angle={} end_angle={} sweep_angle={} segment={:?}", - angle, - start_angle, - end_angle, - sweep_angle, - segment); + let mut segment = Segment::arc_from_cos(sweep_vector.0.x()); + let rotation = + Transform2DF32::from_rotation_vector(sweep_vector.halve_angle().rotate_by(vector)); + segment = segment.transform(&scale.post_mul(&rotation).post_mul(&translation)); if first_segment { self.push_full_segment(&segment, true); @@ -404,8 +414,14 @@ impl Contour { self.push_segment(segment, true); } - angle += sweep_angle; + if last { + break; + } + + vector = sweep_vector.rotate_by(vector); } + + const EPSILON: f32 = 0.001; } #[inline] diff --git a/geometry/src/segment.rs b/geometry/src/segment.rs index 6fdc28eb..f201110c 100644 --- a/geometry/src/segment.rs +++ b/geometry/src/segment.rs @@ -70,14 +70,22 @@ impl Segment { /// Approximates an unit-length arc with a cubic Bézier curve. /// - /// The maximum supported `sweep_angle` is π/2 (i.e. 90°). + /// The maximum supported sweep angle is π/2 (i.e. 90°). pub fn arc(sweep_angle: f32) -> Segment { + Segment::arc_from_cos(f32::cos(sweep_angle)) + } + + /// Approximates an unit-length arc with a cubic Bézier curve, given the cosine of the sweep + /// angle. + /// + /// The maximum supported sweep angle is π/2 (i.e. 90°). + pub fn arc_from_cos(cos_sweep_angle: f32) -> Segment { // Aleksas Riškus, "Approximation of a Cubic Bézier Curve by Circular Arcs and Vice Versa" // 2006. // // https://pdfs.semanticscholar.org/1639/0db1a470bd13fe428e0896671a9a5745070a.pdf - let phi = 0.5 * sweep_angle; - let p0 = Point2DF32::new(f32::cos(phi), f32::sin(phi)); + let term = F32x4::new(cos_sweep_angle, -cos_sweep_angle, 0.0, 0.0); + let p0 = Point2DF32((F32x4::splat(0.5) * (F32x4::splat(1.0) + term)).sqrt()); let p3 = p0.scale_xy(Point2DF32::new(1.0, -1.0)); let p1 = p0 - p3.yx().scale(K); let p2 = p3 + p0.yx().scale(K); diff --git a/geometry/src/stroke.rs b/geometry/src/stroke.rs index 75e51019..a3f06c94 100644 --- a/geometry/src/stroke.rs +++ b/geometry/src/stroke.rs @@ -15,7 +15,6 @@ use crate::basic::point::Point2DF32; use crate::basic::rect::RectF32; use crate::outline::{Contour, Outline}; use crate::segment::Segment; -use std::f32::consts::FRAC_PI_2; use std::f32; use std::mem; @@ -124,10 +123,9 @@ impl OutlineStrokeToFill { contour.push_endpoint(p4); } LineCap::Round => { - // FIXME(pcwalton): Should we really be using angles here at all? - let offset = gradient.yx().scale_xy(Point2DF32::new(-width * 0.5, width * 0.5)); - let angle = f32::atan2(gradient.y(), gradient.x()); - contour.push_arc(p1 + offset, width * 0.5, angle - FRAC_PI_2, angle + FRAC_PI_2); + let offset = gradient.yx().scale_xy(Point2DF32::new(-width, width)); + let chord = LineSegmentF32::new(p1, p1 + offset); + contour.push_arc_from_chord(p1 + offset.scale(0.5), chord); } } } diff --git a/geometry/src/unit_vector.rs b/geometry/src/unit_vector.rs new file mode 100644 index 00000000..ac74d7a3 --- /dev/null +++ b/geometry/src/unit_vector.rs @@ -0,0 +1,46 @@ +// pathfinder/geometry/src/unit_vector.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A utility module that allows unit vectors to be treated like angles. + +use crate::basic::point::Point2DF32; +use pathfinder_simd::default::F32x4; + +#[derive(Clone, Copy, Debug)] +pub struct UnitVector(pub Point2DF32); + +impl UnitVector { + #[inline] + pub fn from_angle(theta: f32) -> UnitVector { + UnitVector(Point2DF32::new(theta.cos(), theta.sin())) + } + + /// Angle addition formula. + #[inline] + pub fn rotate_by(&self, other: UnitVector) -> UnitVector { + let products = (self.0).0.xyyx() * (other.0).0.xyxy(); + UnitVector(Point2DF32::new(products[0] - products[1], products[2] + products[3])) + } + + /// Angle subtraction formula. + #[inline] + pub fn rev_rotate_by(&self, other: UnitVector) -> UnitVector { + let products = (self.0).0.xyyx() * (other.0).0.xyxy(); + UnitVector(Point2DF32::new(products[0] + products[1], products[2] - products[3])) + } + + /// Half angle formula. + #[inline] + pub fn halve_angle(&self) -> UnitVector { + let x = self.0.x(); + let term = F32x4::new(x, -x, 0.0, 0.0); + UnitVector(Point2DF32((F32x4::splat(0.5) * (F32x4::splat(1.0) + term)).sqrt())) + } +} diff --git a/simd/src/arm/mod.rs b/simd/src/arm/mod.rs index 97c990a8..14871608 100644 --- a/simd/src/arm/mod.rs +++ b/simd/src/arm/mod.rs @@ -76,6 +76,11 @@ impl F32x4 { unsafe { F32x4(round_v4f32(self.0)) } } + #[inline] + pub fn sqrt(self) -> F32x4 { + unsafe { F32x4(sqrt_v4f32(self.0)) } + } + // Packed comparisons #[inline] @@ -414,6 +419,8 @@ extern "C" { fn ceil_v4f32(a: float32x4_t) -> float32x4_t; #[link_name = "llvm.round.v4f32"] fn round_v4f32(a: float32x4_t) -> float32x4_t; + #[link_name = "llvm.sqrt.v4f32"] + fn sqrt_v4f32(a: float32x4_t) -> float32x4_t; #[link_name = "llvm.aarch64.neon.frecpe.v4f32"] fn vrecpe_v4f32(a: float32x4_t) -> float32x4_t; diff --git a/simd/src/scalar/mod.rs b/simd/src/scalar/mod.rs index 901d0af0..19049d01 100644 --- a/simd/src/scalar/mod.rs +++ b/simd/src/scalar/mod.rs @@ -99,6 +99,16 @@ impl F32x4 { ]) } + #[inline] + pub fn sqrt(self) -> F32x4 { + F32x4([ + self[0].sqrt(), + self[1].sqrt(), + self[2].sqrt(), + self[3].sqrt(), + ]) + } + // Packed comparisons #[inline] diff --git a/simd/src/x86/mod.rs b/simd/src/x86/mod.rs index 2896b487..af90be53 100644 --- a/simd/src/x86/mod.rs +++ b/simd/src/x86/mod.rs @@ -83,6 +83,11 @@ impl F32x4 { unsafe { F32x4(x86_64::_mm_round_ps(self.0, _MM_FROUND_TO_NEAREST_INT)) } } + #[inline] + pub fn sqrt(self) -> F32x4 { + unsafe { F32x4(x86_64::_mm_sqrt_ps(self.0)) } + } + // Packed comparisons #[inline]