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:
parent
1df5e7a129
commit
5133bbfe1a
|
@ -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))
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -27,3 +27,4 @@ pub mod stroke;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
mod dilation;
|
mod dilation;
|
||||||
|
mod unit_vector;
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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]
|
||||||
|
|
Loading…
Reference in New Issue