mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 12:28:12 +08:00
Dedupe code for BezPath emission, epsilon constants, and Polynomial internals (#4459)
* Collapse duplicated BezPath emission, constants, and Polynomial surface across the vector algorithms * Fix the quadratic segment length lower bound using a control leg instead of the chord
This commit is contained in:
@@ -1,10 +1,9 @@
|
|||||||
use kurbo::PathSeg;
|
use kurbo::PathSeg;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::ops::{Mul, MulAssign};
|
||||||
use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
|
||||||
|
|
||||||
/// A struct that represents a polynomial with a maximum degree of `N-1`.
|
/// A struct that represents a polynomial with a maximum degree of `N-1`.
|
||||||
///
|
///
|
||||||
/// It provides basic mathematical operations for polynomials like addition, multiplication, differentiation, integration, etc.
|
/// It provides basic mathematical operations for polynomials like multiplication, differentiation, integration, etc.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub struct Polynomial<const N: usize> {
|
pub struct Polynomial<const N: usize> {
|
||||||
coefficients: [f64; N],
|
coefficients: [f64; N],
|
||||||
@@ -18,18 +17,6 @@ impl<const N: usize> Polynomial<N> {
|
|||||||
Polynomial { coefficients }
|
Polynomial { coefficients }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a polynomial where all its coefficients are zero.
|
|
||||||
pub fn zero() -> Polynomial<N> {
|
|
||||||
Polynomial { coefficients: [0.; N] }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return an immutable reference to the coefficients.
|
|
||||||
///
|
|
||||||
/// The coefficient for nth degree is at the nth index in array. Therefore the order of coefficients are reversed than the usual order for writing polynomials mathematically.
|
|
||||||
pub fn coefficients(&self) -> &[f64; N] {
|
|
||||||
&self.coefficients
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a mutable reference to the coefficients.
|
/// Return a mutable reference to the coefficients.
|
||||||
///
|
///
|
||||||
/// The coefficient for nth degree is at the nth index in array. Therefore the order of coefficients are reversed than the usual order for writing polynomials mathematically.
|
/// The coefficient for nth degree is at the nth index in array. Therefore the order of coefficients are reversed than the usual order for writing polynomials mathematically.
|
||||||
@@ -83,98 +70,6 @@ impl<const N: usize> Polynomial<N> {
|
|||||||
ans.derivative_mut();
|
ans.derivative_mut();
|
||||||
ans
|
ans
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the antiderivative at `C = 0`.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the polynomial is not big enough to accommodate the extra degree.
|
|
||||||
pub fn antiderivative(&self) -> Option<Polynomial<N>> {
|
|
||||||
let mut ans = *self;
|
|
||||||
ans.antiderivative_mut()?;
|
|
||||||
Some(ans)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Default for Polynomial<N> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::zero()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Display for Polynomial<N> {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
let mut first = true;
|
|
||||||
for (index, coefficient) in self.coefficients.iter().enumerate().rev().filter(|&(_, &coefficient)| coefficient != 0.) {
|
|
||||||
if first {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
f.write_str(" + ")?
|
|
||||||
}
|
|
||||||
|
|
||||||
coefficient.fmt(f)?;
|
|
||||||
if index == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
f.write_str("x")?;
|
|
||||||
if index == 1 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
f.write_str("^")?;
|
|
||||||
index.fmt(f)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> AddAssign<&Polynomial<N>> for Polynomial<N> {
|
|
||||||
fn add_assign(&mut self, rhs: &Polynomial<N>) {
|
|
||||||
self.coefficients.iter_mut().zip(rhs.coefficients.iter()).for_each(|(a, b)| *a += b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Add for &Polynomial<N> {
|
|
||||||
type Output = Polynomial<N>;
|
|
||||||
|
|
||||||
fn add(self, other: &Polynomial<N>) -> Polynomial<N> {
|
|
||||||
let mut output = *self;
|
|
||||||
output += other;
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Neg for &Polynomial<N> {
|
|
||||||
type Output = Polynomial<N>;
|
|
||||||
|
|
||||||
fn neg(self) -> Polynomial<N> {
|
|
||||||
let mut output = *self;
|
|
||||||
output.coefficients.iter_mut().for_each(|x| *x = -*x);
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Neg for Polynomial<N> {
|
|
||||||
type Output = Polynomial<N>;
|
|
||||||
|
|
||||||
fn neg(mut self) -> Polynomial<N> {
|
|
||||||
self.coefficients.iter_mut().for_each(|x| *x = -*x);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> SubAssign<&Polynomial<N>> for Polynomial<N> {
|
|
||||||
fn sub_assign(&mut self, rhs: &Polynomial<N>) {
|
|
||||||
self.coefficients.iter_mut().zip(rhs.coefficients.iter()).for_each(|(a, b)| *a -= b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Sub for &Polynomial<N> {
|
|
||||||
type Output = Polynomial<N>;
|
|
||||||
|
|
||||||
fn sub(self, other: &Polynomial<N>) -> Polynomial<N> {
|
|
||||||
let mut output = *self;
|
|
||||||
output -= other;
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> MulAssign<&Polynomial<N>> for Polynomial<N> {
|
impl<const N: usize> MulAssign<&Polynomial<N>> for Polynomial<N> {
|
||||||
@@ -248,18 +143,6 @@ mod test {
|
|||||||
assert_eq!(p2.as_size::<2>(), None);
|
assert_eq!(p2.as_size::<2>(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn addition_and_subtaction() {
|
|
||||||
let p1 = Polynomial::new([1., 2., 3.]);
|
|
||||||
let p2 = Polynomial::new([4., 5., 6.]);
|
|
||||||
|
|
||||||
let addition = Polynomial::new([5., 7., 9.]);
|
|
||||||
let subtraction = Polynomial::new([-3., -3., -3.]);
|
|
||||||
|
|
||||||
assert_eq!(&p1 + &p2, addition);
|
|
||||||
assert_eq!(&p1 - &p2, subtraction);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiplication() {
|
fn multiplication() {
|
||||||
let p1 = Polynomial::new([1., 2., 3.]).as_size().unwrap();
|
let p1 = Polynomial::new([1., 2., 3.]).as_size().unwrap();
|
||||||
@@ -278,15 +161,10 @@ mod test {
|
|||||||
assert_eq!(p.derivative(), p_deriv);
|
assert_eq!(p.derivative(), p_deriv);
|
||||||
|
|
||||||
p.coefficients_mut()[0] = 0.;
|
p.coefficients_mut()[0] = 0.;
|
||||||
assert_eq!(p_deriv.antiderivative().unwrap(), p);
|
let mut antiderivative = p_deriv;
|
||||||
|
assert_eq!(antiderivative.antiderivative_mut(), Some(()));
|
||||||
|
assert_eq!(antiderivative, p);
|
||||||
|
|
||||||
assert_eq!(p.antiderivative(), None);
|
assert_eq!(p.antiderivative_mut(), None);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn display() {
|
|
||||||
let p = Polynomial::new([1., 2., 0., 3.]);
|
|
||||||
|
|
||||||
assert_eq!(format!("{p:.2}"), "3.00x^3 + 2.00x + 1.00");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
|
use super::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||||
use super::intersection::{bezpath_intersections, filtered_all_segment_intersections, pathseg_self_intersections};
|
use super::intersection::{bezpath_intersections, filtered_all_segment_intersections, pathseg_self_intersections};
|
||||||
use super::poisson_disk::poisson_disk_sample;
|
use super::poisson_disk::poisson_disk_sample;
|
||||||
use super::util::pathseg_tangent;
|
use super::util::pathseg_tangent;
|
||||||
use crate::vector::misc::{PointSpacingType, dvec2_to_point, point_to_dvec2};
|
use crate::vector::misc::{PointSpacingType, dvec2_to_point, point_to_dvec2};
|
||||||
use core_types::math::polynomial::pathseg_to_parametric_polynomial;
|
use core_types::math::polynomial::pathseg_to_parametric_polynomial;
|
||||||
use glam::{DMat2, DVec2};
|
use glam::{DMat2, DVec2};
|
||||||
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveArclen, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape, Vec2};
|
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveArclen, PathEl, PathSeg, Point, QuadBez, Rect, Shape, Vec2};
|
||||||
use std::f64::consts::{FRAC_PI_2, PI};
|
use std::f64::consts::{FRAC_PI_2, PI};
|
||||||
|
|
||||||
/// Default threshold for comparing floating point values in intersection and centroid math.
|
|
||||||
const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-3;
|
|
||||||
|
|
||||||
/// Splits the [`BezPath`] at segment index at `t` value which lie in the range of [0, 1].
|
/// Splits the [`BezPath`] at segment index at `t` value which lie in the range of [0, 1].
|
||||||
/// Returns [`None`] if the given [`BezPath`] has no segments or `t` is within f64::EPSILON of 0 or 1.
|
/// Returns [`None`] if the given [`BezPath`] has no segments or `t` is within f64::EPSILON of 0 or 1.
|
||||||
fn split_bezpath_at_segment(bezpath: &BezPath, segment_index: usize, t: f64) -> Option<(BezPath, BezPath)> {
|
fn split_bezpath_at_segment(bezpath: &BezPath, segment_index: usize, t: f64) -> Option<(BezPath, BezPath)> {
|
||||||
@@ -79,11 +77,7 @@ pub fn tangent_on_bezpath(bezpath: &BezPath, t_value: TValue, segments_length: O
|
|||||||
let (segment_index, t) = eval_bezpath(bezpath, t_value, segments_length);
|
let (segment_index, t) = eval_bezpath(bezpath, t_value, segments_length);
|
||||||
let segment = bezpath.get_seg(segment_index + 1).unwrap();
|
let segment = bezpath.get_seg(segment_index + 1).unwrap();
|
||||||
|
|
||||||
match segment {
|
dvec2_to_point(pathseg_tangent(segment, t))
|
||||||
PathSeg::Line(line) => line.deriv().eval(t),
|
|
||||||
PathSeg::Quad(quad_bez) => quad_bez.deriv().eval(t),
|
|
||||||
PathSeg::Cubic(cubic_bez) => cubic_bez.deriv().eval(t),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes sample locations along a bezpath, returning parametric `(segment_index, t)` pairs and whether the path was closed.
|
/// Computes sample locations along a bezpath, returning parametric `(segment_index, t)` pairs and whether the path was closed.
|
||||||
@@ -254,7 +248,7 @@ pub(crate) fn pathseg_length_centroid_and_length(segment: PathSeg, accuracy: Opt
|
|||||||
let QuadBez { p0, p1, p2 } = quad_bez;
|
let QuadBez { p0, p1, p2 } = quad_bez;
|
||||||
// Use Casteljau subdivision, noting that the length is more than the straight line distance from start to end but less than the straight line distance through the handles
|
// Use Casteljau subdivision, noting that the length is more than the straight line distance from start to end but less than the straight line distance through the handles
|
||||||
fn recurse(a0: Vec2, a1: Vec2, a2: Vec2, accuracy: f64, level: u8) -> (f64, Vec2) {
|
fn recurse(a0: Vec2, a1: Vec2, a2: Vec2, accuracy: f64, level: u8) -> (f64, Vec2) {
|
||||||
let lower = (a2 - a1).length();
|
let lower = (a2 - a0).length();
|
||||||
let upper = (a1 - a0).length() + (a2 - a1).length();
|
let upper = (a1 - a0).length() + (a2 - a1).length();
|
||||||
if upper - lower <= 2. * accuracy || level >= 8 {
|
if upper - lower <= 2. * accuracy || level >= 8 {
|
||||||
let length = (lower + upper) / 2.;
|
let length = (lower + upper) / 2.;
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/// Minimum allowable separation between adjacent `t` values when calculating curve intersections
|
||||||
|
pub(crate) const MIN_SEPARATION_VALUE: f64 = 5. * 1e-3;
|
||||||
|
|
||||||
|
/// Threshold for comparing floating point values in intersection and centroid math.
|
||||||
|
pub(crate) const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-3;
|
||||||
|
|
||||||
|
/// Maximum distance at which two points are treated as one and the same point.
|
||||||
|
pub(crate) const MAX_COINCIDENT_POINT_DISTANCE: f64 = 1e-7;
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
/// Minimum allowable separation between adjacent `t` values when calculating curve intersections
|
|
||||||
pub(crate) const MIN_SEPARATION_VALUE: f64 = 5. * 1e-3;
|
|
||||||
|
|
||||||
/// Constant used to determine if `f64`s are equivalent.
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-3;
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::contants::MIN_SEPARATION_VALUE;
|
use super::consts::MIN_SEPARATION_VALUE;
|
||||||
use kurbo::{BezPath, DEFAULT_ACCURACY, ParamCurve, PathSeg, Shape};
|
use kurbo::{BezPath, DEFAULT_ACCURACY, ParamCurve, PathSeg, Shape};
|
||||||
use lyon_geom::{CubicBezierSegment, Point};
|
use lyon_geom::{CubicBezierSegment, Point};
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ pub(crate) fn pathseg_self_intersections(segment: PathSeg, accuracy: Option<f64>
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{bezpath_and_segment_intersections, filtered_segment_intersections};
|
use super::{bezpath_and_segment_intersections, filtered_segment_intersections};
|
||||||
use crate::vector::algorithms::{
|
use crate::vector::algorithms::{
|
||||||
contants::MAX_ABSOLUTE_DIFFERENCE,
|
consts::MAX_ABSOLUTE_DIFFERENCE,
|
||||||
util::{compare_points, compare_vec_of_points, dvec2_compare},
|
util::{compare_points, compare_vec_of_points, dvec2_compare},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
pub mod bezpath_algorithms;
|
pub mod bezpath_algorithms;
|
||||||
mod contants;
|
pub(crate) mod consts;
|
||||||
pub mod intersection;
|
pub mod intersection;
|
||||||
pub mod merge_by_distance;
|
pub mod merge_by_distance;
|
||||||
pub mod offset_subpath;
|
pub mod offset_bezpath;
|
||||||
mod poisson_disk;
|
mod poisson_disk;
|
||||||
pub mod shapes;
|
pub mod shapes;
|
||||||
pub mod spline;
|
pub mod spline;
|
||||||
|
|||||||
+6
-7
@@ -1,13 +1,12 @@
|
|||||||
use super::bezpath_algorithms::{clip_simple_bezpaths, miter_line_join, round_line_join};
|
use super::bezpath_algorithms::{clip_simple_bezpaths, miter_line_join, round_line_join};
|
||||||
|
use super::consts::MAX_COINCIDENT_POINT_DISTANCE;
|
||||||
use crate::vector::misc::point_to_dvec2;
|
use crate::vector::misc::point_to_dvec2;
|
||||||
use kurbo::{BezPath, Join, ParamCurve, PathEl, PathSeg};
|
use kurbo::{BezPath, Join, ParamCurve, PathEl, PathSeg};
|
||||||
|
|
||||||
/// Value to control smoothness and mathematical accuracy to offset a cubic Bezier.
|
/// Value to control smoothness and mathematical accuracy to offset a cubic Bezier.
|
||||||
const CUBIC_REGULARIZATION_ACCURACY: f64 = 0.5;
|
const CUBIC_REGULARIZATION_ACCURACY: f64 = 0.5;
|
||||||
/// Constant used to determine if `f64`s are equivalent.
|
|
||||||
pub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-7;
|
|
||||||
/// Squared version to avoid sqrt in distance checks.
|
/// Squared version to avoid sqrt in distance checks.
|
||||||
const MAX_ABSOLUTE_DIFFERENCE_SQUARED: f64 = MAX_ABSOLUTE_DIFFERENCE * MAX_ABSOLUTE_DIFFERENCE;
|
const MAX_COINCIDENT_POINT_DISTANCE_SQUARED: f64 = MAX_COINCIDENT_POINT_DISTANCE * MAX_COINCIDENT_POINT_DISTANCE;
|
||||||
const MAX_FITTED_SEGMENTS: usize = 10000;
|
const MAX_FITTED_SEGMENTS: usize = 10000;
|
||||||
|
|
||||||
/// Reduces the segments of the bezpath into simple subcurves, then offset each subcurve a set `distance` away.
|
/// Reduces the segments of the bezpath into simple subcurves, then offset each subcurve a set `distance` away.
|
||||||
@@ -26,9 +25,9 @@ pub fn offset_bezpath(bezpath: &BezPath, distance: f64, join: Join, miter_limit:
|
|||||||
// Skip degenerate curves where all control points are at the same location.
|
// Skip degenerate curves where all control points are at the same location.
|
||||||
// Offsetting a point is undefined and causes infinite recursion in fit_to_bezpath.
|
// Offsetting a point is undefined and causes infinite recursion in fit_to_bezpath.
|
||||||
let start = cubic_bez.p0;
|
let start = cubic_bez.p0;
|
||||||
let is_degenerate = start.distance_squared(cubic_bez.p1) < MAX_ABSOLUTE_DIFFERENCE_SQUARED
|
let is_degenerate = start.distance_squared(cubic_bez.p1) < MAX_COINCIDENT_POINT_DISTANCE_SQUARED
|
||||||
&& start.distance_squared(cubic_bez.p2) < MAX_ABSOLUTE_DIFFERENCE_SQUARED
|
&& start.distance_squared(cubic_bez.p2) < MAX_COINCIDENT_POINT_DISTANCE_SQUARED
|
||||||
&& start.distance_squared(cubic_bez.p3) < MAX_ABSOLUTE_DIFFERENCE_SQUARED;
|
&& start.distance_squared(cubic_bez.p3) < MAX_COINCIDENT_POINT_DISTANCE_SQUARED;
|
||||||
|
|
||||||
if is_degenerate {
|
if is_degenerate {
|
||||||
return None;
|
return None;
|
||||||
@@ -59,7 +58,7 @@ pub fn offset_bezpath(bezpath: &BezPath, distance: f64, join: Join, miter_limit:
|
|||||||
let first_segment_start = point_to_dvec2(bezpath2.segments().next().unwrap().start());
|
let first_segment_start = point_to_dvec2(bezpath2.segments().next().unwrap().start());
|
||||||
|
|
||||||
// If the anchors are approximately equal, there is no need to clip / join the segments
|
// If the anchors are approximately equal, there is no need to clip / join the segments
|
||||||
if last_segment_end.abs_diff_eq(first_segment_start, MAX_ABSOLUTE_DIFFERENCE) {
|
if last_segment_end.abs_diff_eq(first_segment_start, MAX_COINCIDENT_POINT_DISTANCE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Anchor order and winding direction are load-bearing, since fills rely on every generator agreeing.
|
//! Anchor order and winding direction are load-bearing, since fills rely on every generator agreeing.
|
||||||
|
|
||||||
use crate::vector::misc::{ArcType, SpiralType, dvec2_to_point};
|
use crate::vector::misc::{ArcType, SpiralType, bezpath_from_anchors_and_handles};
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use kurbo::BezPath;
|
use kurbo::BezPath;
|
||||||
use std::f64::consts::TAU;
|
use std::f64::consts::TAU;
|
||||||
@@ -28,31 +28,8 @@ impl Anchor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stitches anchors into a path, emitting a cubic when both facing handles exist, a quadratic when only one does, and a line otherwise.
|
|
||||||
fn bezpath_from_anchors(anchors: &[Anchor], closed: bool) -> BezPath {
|
fn bezpath_from_anchors(anchors: &[Anchor], closed: bool) -> BezPath {
|
||||||
let mut bezpath = BezPath::new();
|
bezpath_from_anchors_and_handles(anchors.iter().map(|anchor| (anchor.position, anchor.in_handle, anchor.out_handle)), closed)
|
||||||
|
|
||||||
let Some(first) = anchors.first() else { return bezpath };
|
|
||||||
bezpath.move_to(dvec2_to_point(first.position));
|
|
||||||
let mut out_handle = first.out_handle;
|
|
||||||
|
|
||||||
let connect_to = |bezpath: &mut BezPath, out_handle: Option<DVec2>, anchor: &Anchor| match (out_handle, anchor.in_handle) {
|
|
||||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(anchor.position)),
|
|
||||||
(None, None) => bezpath.line_to(dvec2_to_point(anchor.position)),
|
|
||||||
(None, Some(handle)) | (Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(anchor.position)),
|
|
||||||
};
|
|
||||||
|
|
||||||
for anchor in anchors.iter().skip(1) {
|
|
||||||
connect_to(&mut bezpath, out_handle, anchor);
|
|
||||||
out_handle = anchor.out_handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if closed {
|
|
||||||
connect_to(&mut bezpath, out_handle, first);
|
|
||||||
bezpath.close_path();
|
|
||||||
}
|
|
||||||
|
|
||||||
bezpath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stitches a sequence of sharp (handleless) anchors into a polyline, or a closed polygon.
|
/// Stitches a sequence of sharp (handleless) anchors into a polyline, or a closed polygon.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub fn pathseg_tangent(segment: PathSeg, t: f64) -> DVec2 {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn compare_points(p1: kurbo::Point, p2: kurbo::Point) -> bool {
|
pub(crate) fn compare_points(p1: kurbo::Point, p2: kurbo::Point) -> bool {
|
||||||
let (p1, p2) = (crate::vector::misc::point_to_dvec2(p1), crate::vector::misc::point_to_dvec2(p2));
|
let (p1, p2) = (crate::vector::misc::point_to_dvec2(p1), crate::vector::misc::point_to_dvec2(p2));
|
||||||
p1.abs_diff_eq(p2, super::contants::MAX_ABSOLUTE_DIFFERENCE)
|
p1.abs_diff_eq(p2, super::consts::MAX_ABSOLUTE_DIFFERENCE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compare vectors of points by allowing some maximum absolute difference to account for floating point errors
|
/// Compare vectors of points by allowing some maximum absolute difference to account for floating point errors
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::PointId;
|
use super::PointId;
|
||||||
use super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
|
use super::algorithms::consts::MAX_COINCIDENT_POINT_DISTANCE;
|
||||||
use crate::vector::{SegmentId, Vector};
|
use crate::vector::{SegmentId, Vector};
|
||||||
use core_types::list::{Item, List};
|
use core_types::list::{Item, List};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
@@ -227,36 +227,42 @@ pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bezpath_from_manipulator_groups(manipulator_groups: &[ManipulatorGroup], closed: bool) -> BezPath {
|
/// Stitches anchors into a path, emitting a cubic when both facing handles exist, a quadratic when only one does, and a line otherwise.
|
||||||
let mut bezpath = kurbo::BezPath::new();
|
///
|
||||||
let mut out_handle;
|
/// Each item is an anchor position paired with its incoming and outgoing handle positions, in absolute coordinates.
|
||||||
|
pub fn bezpath_from_anchors_and_handles(anchors: impl IntoIterator<Item = (DVec2, Option<DVec2>, Option<DVec2>)>, closed: bool) -> BezPath {
|
||||||
|
let mut bezpath = BezPath::new();
|
||||||
|
let mut anchors = anchors.into_iter();
|
||||||
|
|
||||||
let Some(first) = manipulator_groups.first() else { return bezpath };
|
let Some((first_anchor, first_in_handle, first_out_handle)) = anchors.next() else {
|
||||||
bezpath.move_to(dvec2_to_point(first.anchor));
|
return bezpath;
|
||||||
out_handle = first.out_handle;
|
};
|
||||||
|
bezpath.move_to(dvec2_to_point(first_anchor));
|
||||||
|
let mut out_handle = first_out_handle;
|
||||||
|
|
||||||
for manipulator in manipulator_groups.iter().skip(1) {
|
let connect_to = |bezpath: &mut BezPath, out_handle: Option<DVec2>, anchor: DVec2, in_handle: Option<DVec2>| match (out_handle, in_handle) {
|
||||||
match (out_handle, manipulator.in_handle) {
|
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(anchor)),
|
||||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)),
|
(None, None) => bezpath.line_to(dvec2_to_point(anchor)),
|
||||||
(None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)),
|
(None, Some(handle)) | (Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(anchor)),
|
||||||
(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
|
};
|
||||||
(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
|
|
||||||
}
|
for (anchor, in_handle, anchor_out_handle) in anchors {
|
||||||
out_handle = manipulator.out_handle;
|
connect_to(&mut bezpath, out_handle, anchor, in_handle);
|
||||||
|
out_handle = anchor_out_handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
if closed {
|
if closed {
|
||||||
match (out_handle, first.in_handle) {
|
connect_to(&mut bezpath, out_handle, first_anchor, first_in_handle);
|
||||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)),
|
|
||||||
(None, None) => bezpath.line_to(dvec2_to_point(first.anchor)),
|
|
||||||
(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
|
|
||||||
(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
|
|
||||||
}
|
|
||||||
bezpath.close_path();
|
bezpath.close_path();
|
||||||
}
|
}
|
||||||
|
|
||||||
bezpath
|
bezpath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bezpath_from_manipulator_groups(manipulator_groups: &[ManipulatorGroup], closed: bool) -> BezPath {
|
||||||
|
bezpath_from_anchors_and_handles(manipulator_groups.iter().map(|group| (group.anchor, group.in_handle, group.out_handle)), closed)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bezpath_to_manipulator_groups(bezpath: &BezPath) -> (Vec<ManipulatorGroup>, bool) {
|
pub fn bezpath_to_manipulator_groups(bezpath: &BezPath) -> (Vec<ManipulatorGroup>, bool) {
|
||||||
let mut manipulator_groups = Vec::<ManipulatorGroup>::new();
|
let mut manipulator_groups = Vec::<ManipulatorGroup>::new();
|
||||||
let mut is_closed = false;
|
let mut is_closed = false;
|
||||||
@@ -293,7 +299,7 @@ pub fn bezpath_to_manipulator_groups(bezpath: &BezPath) -> (Vec<ManipulatorGroup
|
|||||||
///
|
///
|
||||||
/// This is different from simply checking if the segment is [`PathSeg::Line`] or [`PathSeg::Quad`] or [`PathSeg::Cubic`]. Bezier curve can also be a line if the control points are colinear to the start and end points. Therefore if the handles exceed the start and end point, it will still be considered as a line.
|
/// This is different from simply checking if the segment is [`PathSeg::Line`] or [`PathSeg::Quad`] or [`PathSeg::Cubic`]. Bezier curve can also be a line if the control points are colinear to the start and end points. Therefore if the handles exceed the start and end point, it will still be considered as a line.
|
||||||
pub fn is_linear(segment: PathSeg) -> bool {
|
pub fn is_linear(segment: PathSeg) -> bool {
|
||||||
let is_colinear = |a: Point, b: Point, c: Point| -> bool { ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)).abs() < MAX_ABSOLUTE_DIFFERENCE };
|
let is_colinear = |a: Point, b: Point, c: Point| -> bool { ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)).abs() < MAX_COINCIDENT_POINT_DISTANCE };
|
||||||
|
|
||||||
match segment {
|
match segment {
|
||||||
PathSeg::Line(_) => true,
|
PathSeg::Line(_) => true,
|
||||||
|
|||||||
@@ -1043,35 +1043,8 @@ impl Vector {
|
|||||||
|
|
||||||
/// Construct a [`kurbo::BezPath`] curve for stroke.
|
/// Construct a [`kurbo::BezPath`] curve for stroke.
|
||||||
pub fn stroke_bezpath_iter(&self) -> impl Iterator<Item = kurbo::BezPath> {
|
pub fn stroke_bezpath_iter(&self) -> impl Iterator<Item = kurbo::BezPath> {
|
||||||
self.build_stroke_path_iter().map(|(manipulators_list, closed)| {
|
self.build_stroke_path_iter()
|
||||||
let mut bezpath = kurbo::BezPath::new();
|
.map(|(manipulators_list, closed)| crate::vector::misc::bezpath_from_manipulator_groups(&manipulators_list, closed))
|
||||||
let mut out_handle;
|
|
||||||
|
|
||||||
let Some(first) = manipulators_list.first() else { return bezpath };
|
|
||||||
bezpath.move_to(dvec2_to_point(first.anchor));
|
|
||||||
out_handle = first.out_handle;
|
|
||||||
|
|
||||||
for manipulator in manipulators_list.iter().skip(1) {
|
|
||||||
match (out_handle, manipulator.in_handle) {
|
|
||||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)),
|
|
||||||
(None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)),
|
|
||||||
(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
|
|
||||||
(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
|
|
||||||
}
|
|
||||||
out_handle = manipulator.out_handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if closed {
|
|
||||||
match (out_handle, first.in_handle) {
|
|
||||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)),
|
|
||||||
(None, None) => bezpath.line_to(dvec2_to_point(first.anchor)),
|
|
||||||
(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
|
|
||||||
(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
|
|
||||||
}
|
|
||||||
bezpath.close_path();
|
|
||||||
}
|
|
||||||
bezpath
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform(&mut self, transform: DAffine2) {
|
pub fn transform(&mut self, transform: DAffine2) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ use vector_types::vector::algorithms::bezpath_algorithms::{
|
|||||||
self, TValue, bezpath_area_centroid_and_area, bezpath_length_centroid_and_length, eval_pathseg_euclidean, evaluate_bezpath, split_bezpath, tangent_on_bezpath,
|
self, TValue, bezpath_area_centroid_and_area, bezpath_length_centroid_and_length, eval_pathseg_euclidean, evaluate_bezpath, split_bezpath, tangent_on_bezpath,
|
||||||
};
|
};
|
||||||
use vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
use vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
||||||
use vector_types::vector::algorithms::offset_subpath::offset_bezpath;
|
use vector_types::vector::algorithms::offset_bezpath::offset_bezpath;
|
||||||
use vector_types::vector::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
|
use vector_types::vector::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
|
||||||
use vector_types::vector::misc::{
|
use vector_types::vector::misc::{
|
||||||
BezierHandles, CentroidType, ExtrudeJoiningAlgorithm, HandleId, InterpolationDistribution, ManipulatorGroup, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns,
|
BezierHandles, CentroidType, ExtrudeJoiningAlgorithm, HandleId, InterpolationDistribution, ManipulatorGroup, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns,
|
||||||
|
|||||||
Reference in New Issue
Block a user