mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +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 std::fmt::{self, Display, Formatter};
|
||||
use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
use std::ops::{Mul, MulAssign};
|
||||
|
||||
/// 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)]
|
||||
pub struct Polynomial<const N: usize> {
|
||||
coefficients: [f64; N],
|
||||
@@ -18,18 +17,6 @@ impl<const N: usize> Polynomial<N> {
|
||||
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.
|
||||
///
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
@@ -248,18 +143,6 @@ mod test {
|
||||
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]
|
||||
fn multiplication() {
|
||||
let p1 = Polynomial::new([1., 2., 3.]).as_size().unwrap();
|
||||
@@ -278,15 +161,10 @@ mod test {
|
||||
assert_eq!(p.derivative(), p_deriv);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
let p = Polynomial::new([1., 2., 0., 3.]);
|
||||
|
||||
assert_eq!(format!("{p:.2}"), "3.00x^3 + 2.00x + 1.00");
|
||||
assert_eq!(p.antiderivative_mut(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
use super::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use super::intersection::{bezpath_intersections, filtered_all_segment_intersections, pathseg_self_intersections};
|
||||
use super::poisson_disk::poisson_disk_sample;
|
||||
use super::util::pathseg_tangent;
|
||||
use crate::vector::misc::{PointSpacingType, dvec2_to_point, point_to_dvec2};
|
||||
use core_types::math::polynomial::pathseg_to_parametric_polynomial;
|
||||
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};
|
||||
|
||||
/// 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].
|
||||
/// 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)> {
|
||||
@@ -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 = bezpath.get_seg(segment_index + 1).unwrap();
|
||||
|
||||
match segment {
|
||||
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),
|
||||
}
|
||||
dvec2_to_point(pathseg_tangent(segment, t))
|
||||
}
|
||||
|
||||
/// 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;
|
||||
// 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) {
|
||||
let lower = (a2 - a1).length();
|
||||
let lower = (a2 - a0).length();
|
||||
let upper = (a1 - a0).length() + (a2 - a1).length();
|
||||
if upper - lower <= 2. * accuracy || level >= 8 {
|
||||
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 lyon_geom::{CubicBezierSegment, Point};
|
||||
|
||||
@@ -260,7 +260,7 @@ pub(crate) fn pathseg_self_intersections(segment: PathSeg, accuracy: Option<f64>
|
||||
mod tests {
|
||||
use super::{bezpath_and_segment_intersections, filtered_segment_intersections};
|
||||
use crate::vector::algorithms::{
|
||||
contants::MAX_ABSOLUTE_DIFFERENCE,
|
||||
consts::MAX_ABSOLUTE_DIFFERENCE,
|
||||
util::{compare_points, compare_vec_of_points, dvec2_compare},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
pub mod bezpath_algorithms;
|
||||
mod contants;
|
||||
pub(crate) mod consts;
|
||||
pub mod intersection;
|
||||
pub mod merge_by_distance;
|
||||
pub mod offset_subpath;
|
||||
pub mod offset_bezpath;
|
||||
mod poisson_disk;
|
||||
pub mod shapes;
|
||||
pub mod spline;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
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 kurbo::{BezPath, Join, ParamCurve, PathEl, PathSeg};
|
||||
|
||||
/// Value to control smoothness and mathematical accuracy to offset a cubic Bezier.
|
||||
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.
|
||||
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;
|
||||
|
||||
/// 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.
|
||||
// Offsetting a point is undefined and causes infinite recursion in fit_to_bezpath.
|
||||
let start = cubic_bez.p0;
|
||||
let is_degenerate = start.distance_squared(cubic_bez.p1) < MAX_ABSOLUTE_DIFFERENCE_SQUARED
|
||||
&& start.distance_squared(cubic_bez.p2) < MAX_ABSOLUTE_DIFFERENCE_SQUARED
|
||||
&& start.distance_squared(cubic_bez.p3) < 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_COINCIDENT_POINT_DISTANCE_SQUARED
|
||||
&& start.distance_squared(cubic_bez.p3) < MAX_COINCIDENT_POINT_DISTANCE_SQUARED;
|
||||
|
||||
if is_degenerate {
|
||||
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());
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! 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 kurbo::BezPath;
|
||||
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 {
|
||||
let mut bezpath = BezPath::new();
|
||||
|
||||
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
|
||||
bezpath_from_anchors_and_handles(anchors.iter().map(|anchor| (anchor.position, anchor.in_handle, anchor.out_handle)), closed)
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
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));
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 core_types::list::{Item, List};
|
||||
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 {
|
||||
let mut bezpath = kurbo::BezPath::new();
|
||||
let mut out_handle;
|
||||
/// Stitches anchors into a path, emitting a cubic when both facing handles exist, a quadratic when only one does, and a line otherwise.
|
||||
///
|
||||
/// 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 };
|
||||
bezpath.move_to(dvec2_to_point(first.anchor));
|
||||
out_handle = first.out_handle;
|
||||
let Some((first_anchor, first_in_handle, first_out_handle)) = anchors.next() else {
|
||||
return bezpath;
|
||||
};
|
||||
bezpath.move_to(dvec2_to_point(first_anchor));
|
||||
let mut out_handle = first_out_handle;
|
||||
|
||||
for manipulator in manipulator_groups.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;
|
||||
let connect_to = |bezpath: &mut BezPath, out_handle: Option<DVec2>, anchor: DVec2, in_handle: Option<DVec2>| match (out_handle, 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)),
|
||||
(None, None) => bezpath.line_to(dvec2_to_point(anchor)),
|
||||
(None, Some(handle)) | (Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(anchor)),
|
||||
};
|
||||
|
||||
for (anchor, in_handle, anchor_out_handle) in anchors {
|
||||
connect_to(&mut bezpath, out_handle, anchor, in_handle);
|
||||
out_handle = anchor_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)),
|
||||
}
|
||||
connect_to(&mut bezpath, out_handle, first_anchor, first_in_handle);
|
||||
bezpath.close_path();
|
||||
}
|
||||
|
||||
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) {
|
||||
let mut manipulator_groups = Vec::<ManipulatorGroup>::new();
|
||||
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.
|
||||
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 {
|
||||
PathSeg::Line(_) => true,
|
||||
|
||||
@@ -1043,35 +1043,8 @@ impl Vector {
|
||||
|
||||
/// Construct a [`kurbo::BezPath`] curve for stroke.
|
||||
pub fn stroke_bezpath_iter(&self) -> impl Iterator<Item = kurbo::BezPath> {
|
||||
self.build_stroke_path_iter().map(|(manipulators_list, closed)| {
|
||||
let mut bezpath = kurbo::BezPath::new();
|
||||
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
|
||||
})
|
||||
self.build_stroke_path_iter()
|
||||
.map(|(manipulators_list, closed)| crate::vector::misc::bezpath_from_manipulator_groups(&manipulators_list, closed))
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
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::misc::{
|
||||
BezierHandles, CentroidType, ExtrudeJoiningAlgorithm, HandleId, InterpolationDistribution, ManipulatorGroup, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns,
|
||||
|
||||
Reference in New Issue
Block a user