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:
Keavon Chambers
2026-08-18 16:24:12 -07:00
committed by GitHub
parent 5c580f9dac
commit c19f79977b
12 changed files with 62 additions and 233 deletions
@@ -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;
@@ -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) {
+1 -1
View File
@@ -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,