Use unit vectors instead of angles for arcs.

This makes things faster and will also simplify the implementation of round
joins.
This commit is contained in:
Patrick Walton 2019-05-29 12:12:12 -07:00
parent 1df5e7a129
commit 5133bbfe1a
10 changed files with 145 additions and 36 deletions

View File

@ -87,6 +87,11 @@ impl LineSegmentF32 {
self.0[3] = y self.0[3] = y
} }
#[inline]
pub fn translate(&self, offset: Point2DF32) -> LineSegmentF32 {
LineSegmentF32(self.0 + offset.0.xyxy())
}
#[inline] #[inline]
pub fn scale(&self, factor: f32) -> LineSegmentF32 { pub fn scale(&self, factor: f32) -> LineSegmentF32 {
LineSegmentF32(self.0 * F32x4::splat(factor)) LineSegmentF32(self.0 * F32x4::splat(factor))

View File

@ -15,6 +15,7 @@ use crate::basic::point::Point2DF32;
use crate::basic::rect::RectF32; use crate::basic::rect::RectF32;
use crate::basic::transform3d::Transform3DF32; use crate::basic::transform3d::Transform3DF32;
use crate::segment::Segment; use crate::segment::Segment;
use crate::unit_vector::UnitVector;
use pathfinder_simd::default::F32x4; use pathfinder_simd::default::F32x4;
use std::ops::Sub; use std::ops::Sub;
@ -37,8 +38,12 @@ impl Matrix2x2F32 {
#[inline] #[inline]
pub fn from_rotation(theta: f32) -> Matrix2x2F32 { pub fn from_rotation(theta: f32) -> Matrix2x2F32 {
let (sin_theta, cos_theta) = (theta.sin(), theta.cos()); Matrix2x2F32::from_rotation_vector(UnitVector::from_angle(theta))
Matrix2x2F32(F32x4::new(cos_theta, sin_theta, -sin_theta, cos_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] #[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] #[inline]
pub fn from_translation(vector: Point2DF32) -> Transform2DF32 { pub fn from_translation(vector: Point2DF32) -> Transform2DF32 {
Transform2DF32 { matrix: Matrix2x2F32::default(), vector } Transform2DF32 { matrix: Matrix2x2F32::default(), vector }

View File

@ -27,3 +27,4 @@ pub mod stroke;
pub mod util; pub mod util;
mod dilation; mod dilation;
mod unit_vector;

View File

@ -19,12 +19,11 @@ use crate::clip::{self, ContourPolygonClipper, ContourRectClipper};
use crate::dilation::ContourDilator; use crate::dilation::ContourDilator;
use crate::orientation::Orientation; use crate::orientation::Orientation;
use crate::segment::{Segment, SegmentFlags, SegmentKind}; 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::fmt::{self, Debug, Formatter};
use std::mem; use std::mem;
const TWO_PI: f32 = PI * 2.0;
#[derive(Clone)] #[derive(Clone)]
pub struct Outline { pub struct Outline {
pub(crate) contours: Vec<Contour>, pub(crate) contours: Vec<Contour>,
@ -369,33 +368,44 @@ impl Contour {
self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds); self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds);
} }
pub fn push_arc(&mut self, pub fn push_arc(&mut self, center: Point2DF32, radius: f32, start_angle: f32, end_angle: f32) {
center: Point2DF32, let start = Point2DF32::new(f32::cos(start_angle), f32::sin(start_angle)).scale(radius);
radius: f32, let end = Point2DF32::new(f32::cos(end_angle), f32::sin(end_angle)).scale(radius);
mut start_angle: f32, let chord = LineSegmentF32::new(start, end).translate(center);
mut end_angle: f32) { let full_circle = end_angle - start_angle >= PI * 2.0;
start_angle %= TWO_PI; self.push_arc_from_chord_full(center, chord, full_circle);
end_angle %= TWO_PI; }
if end_angle <= start_angle {
end_angle += TWO_PI; #[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 scale = Transform2DF32::from_scale(Point2DF32::splat(radius));
let translation = Transform2DF32::from_translation(center); let translation = Transform2DF32::from_translation(center);
let (mut angle, mut first_segment) = (start_angle, true); let mut first_segment = true;
while angle < end_angle { loop {
let sweep_angle = f32::min(FRAC_PI_2, end_angle - angle); let mut sweep_vector = end_vector.rev_rotate_by(vector);
let mut segment = Segment::arc(sweep_angle); let last = !(full_circle && first_segment) &&
let rotation = Transform2DF32::from_rotation(sweep_angle * 0.5 + angle); sweep_vector.0.x() >= -EPSILON && sweep_vector.0.y() >= -EPSILON;
segment = segment.transform(&scale.post_mul(&rotation).post_mul(&translation)); if !last {
sweep_vector = UnitVector(Point2DF32::new(0.0, 1.0));
}
debug!("angle={} start_angle={} end_angle={} sweep_angle={} segment={:?}", let mut segment = Segment::arc_from_cos(sweep_vector.0.x());
angle, let rotation =
start_angle, Transform2DF32::from_rotation_vector(sweep_vector.halve_angle().rotate_by(vector));
end_angle, segment = segment.transform(&scale.post_mul(&rotation).post_mul(&translation));
sweep_angle,
segment);
if first_segment { if first_segment {
self.push_full_segment(&segment, true); self.push_full_segment(&segment, true);
@ -404,8 +414,14 @@ impl Contour {
self.push_segment(segment, true); self.push_segment(segment, true);
} }
angle += sweep_angle; if last {
break;
}
vector = sweep_vector.rotate_by(vector);
} }
const EPSILON: f32 = 0.001;
} }
#[inline] #[inline]

View File

@ -70,14 +70,22 @@ impl Segment {
/// Approximates an unit-length arc with a cubic Bézier curve. /// 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 { 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" // Aleksas Riškus, "Approximation of a Cubic Bézier Curve by Circular Arcs and Vice Versa"
// 2006. // 2006.
// //
// https://pdfs.semanticscholar.org/1639/0db1a470bd13fe428e0896671a9a5745070a.pdf // https://pdfs.semanticscholar.org/1639/0db1a470bd13fe428e0896671a9a5745070a.pdf
let phi = 0.5 * sweep_angle; let term = F32x4::new(cos_sweep_angle, -cos_sweep_angle, 0.0, 0.0);
let p0 = Point2DF32::new(f32::cos(phi), f32::sin(phi)); 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 p3 = p0.scale_xy(Point2DF32::new(1.0, -1.0));
let p1 = p0 - p3.yx().scale(K); let p1 = p0 - p3.yx().scale(K);
let p2 = p3 + p0.yx().scale(K); let p2 = p3 + p0.yx().scale(K);

View File

@ -15,7 +15,6 @@ use crate::basic::point::Point2DF32;
use crate::basic::rect::RectF32; use crate::basic::rect::RectF32;
use crate::outline::{Contour, Outline}; use crate::outline::{Contour, Outline};
use crate::segment::Segment; use crate::segment::Segment;
use std::f32::consts::FRAC_PI_2;
use std::f32; use std::f32;
use std::mem; use std::mem;
@ -124,10 +123,9 @@ impl OutlineStrokeToFill {
contour.push_endpoint(p4); contour.push_endpoint(p4);
} }
LineCap::Round => { LineCap::Round => {
// FIXME(pcwalton): Should we really be using angles here at all? let offset = gradient.yx().scale_xy(Point2DF32::new(-width, width));
let offset = gradient.yx().scale_xy(Point2DF32::new(-width * 0.5, width * 0.5)); let chord = LineSegmentF32::new(p1, p1 + offset);
let angle = f32::atan2(gradient.y(), gradient.x()); contour.push_arc_from_chord(p1 + offset.scale(0.5), chord);
contour.push_arc(p1 + offset, width * 0.5, angle - FRAC_PI_2, angle + FRAC_PI_2);
} }
} }
} }

View File

@ -0,0 +1,46 @@
// pathfinder/geometry/src/unit_vector.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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()))
}
}

View File

@ -76,6 +76,11 @@ impl F32x4 {
unsafe { F32x4(round_v4f32(self.0)) } unsafe { F32x4(round_v4f32(self.0)) }
} }
#[inline]
pub fn sqrt(self) -> F32x4 {
unsafe { F32x4(sqrt_v4f32(self.0)) }
}
// Packed comparisons // Packed comparisons
#[inline] #[inline]
@ -414,6 +419,8 @@ extern "C" {
fn ceil_v4f32(a: float32x4_t) -> float32x4_t; fn ceil_v4f32(a: float32x4_t) -> float32x4_t;
#[link_name = "llvm.round.v4f32"] #[link_name = "llvm.round.v4f32"]
fn round_v4f32(a: float32x4_t) -> float32x4_t; 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"] #[link_name = "llvm.aarch64.neon.frecpe.v4f32"]
fn vrecpe_v4f32(a: float32x4_t) -> float32x4_t; fn vrecpe_v4f32(a: float32x4_t) -> float32x4_t;

View File

@ -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 // Packed comparisons
#[inline] #[inline]

View File

@ -83,6 +83,11 @@ impl F32x4 {
unsafe { F32x4(x86_64::_mm_round_ps(self.0, _MM_FROUND_TO_NEAREST_INT)) } 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 // Packed comparisons
#[inline] #[inline]