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
}
#[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))

View File

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

View File

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

View File

@ -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<Contour>,
@ -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]

View File

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

View File

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

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)) }
}
#[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;

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
#[inline]

View File

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