Refactor the Centroid node and Subpath struct and methods to use Kurbo, eliminating all remaining usages of Bezier-rs (#3036)

* define Subpath struct in gcore and refactor node-graph

* Refactor few methods

* refactoring worked!

* refactor centoid area and length

* remove unused

* cleanup

* fix pathseg_points function

* fix tranforming segments

* fix segment intersection

* refactor to_path_segments fn in gpath-bool crate

* refactor gcraft

* add bezier-rs dep

* Code review the editor directory

* use path-bool for solving roots

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Priyanshu
2025-08-17 02:09:25 +05:30
committed by GitHub
parent 99984fc2d6
commit d22b2ca927
60 changed files with 2126 additions and 453 deletions

View File

@@ -1,10 +1,12 @@
use super::intersection::bezpath_intersections;
use super::poisson_disk::poisson_disk_sample;
use super::util::segment_tangent;
use super::util::pathseg_tangent;
use crate::math::polynomial::pathseg_to_parametric_polynomial;
use crate::vector::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
use crate::vector::misc::{PointSpacingType, dvec2_to_point, point_to_dvec2};
use glam::{DMat2, DVec2};
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape};
use kurbo::common::{solve_cubic, solve_quadratic};
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape, Vec2};
use std::f64::consts::{FRAC_PI_2, PI};
/// Splits the [`BezPath`] at segment index at `t` value which lie in the range of [0, 1].
@@ -187,6 +189,82 @@ pub enum TValue {
Euclidean(f64),
}
/// Default LUT step size in `compute_lookup_table` function.
pub const DEFAULT_LUT_STEP_SIZE: usize = 10;
/// Return a selection of equidistant points on the bezier curve.
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
pub fn pathseg_compute_lookup_table(segment: PathSeg, steps: Option<usize>, eucliean: bool) -> impl Iterator<Item = DVec2> {
let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE);
(0..=steps).map(move |t| {
let tvalue = if eucliean {
TValue::Euclidean(t as f64 / steps as f64)
} else {
TValue::Parametric(t as f64 / steps as f64)
};
let t = eval_pathseg(segment, tvalue);
point_to_dvec2(segment.eval(t))
})
}
/// Returns an `Iterator` containing all possible parametric `t`-values at the given `x`-coordinate.
pub fn pathseg_find_tvalues_for_x(segment: PathSeg, x: f64) -> impl Iterator<Item = f64> + use<> {
match segment {
PathSeg::Line(Line { p0, p1 }) => {
// If the transformed linear bezier is on the x-axis, `a` and `b` will both be zero and `solve_linear` will return no roots
let a = p1.x - p0.x;
let b = p0.x - x;
// Find the roots of the linear equation `ax + b`.
// There exist roots when `a` is not 0
if a.abs() > MAX_ABSOLUTE_DIFFERENCE { [Some(-b / a), None, None] } else { [None; 3] }
}
PathSeg::Quad(QuadBez { p0, p1, p2 }) => {
let a = p2.x - 2.0 * p1.x + p0.x;
let b = 2.0 * (p1.x - p0.x);
let c = p0.x - x;
let r = solve_quadratic(c, b, a);
[r.get(0).map(|t| *t), r.get(1).map(|t| *t), None]
}
PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => {
let a = p3.x - 3.0 * p2.x + 3.0 * p1.x - p0.x;
let b = 3.0 * (p2.x - 2.0 * p1.x + p0.x);
let c = 3.0 * (p1.x - p0.x);
let d = p0.x - x;
let r = solve_cubic(d, c, b, a);
[r.get(0).map(|t| *t), r.get(1).map(|t| *t), r.get(2).map(|t| *t)]
}
}
.into_iter()
.flatten()
.filter(|&t| (0.0..1.).contains(&t))
}
/// Find the `t`-value(s) such that the normal(s) at `t` pass through the specified point.
pub fn pathseg_normals_to_point(segment: PathSeg, point: Point) -> Vec<f64> {
// We solve deriv(t) dot (self(t) - point) = 0.
let (mut x, mut y) = pathseg_to_parametric_polynomial(segment);
let x = x.coefficients_mut();
let y = y.coefficients_mut();
x[0] -= point.x;
y[0] -= point.y;
let poly = poly_cool::Poly::new([
x[0] * x[1] + y[0] * y[1],
x[1] * x[1] + y[1] * y[1] + 2. * (x[0] * x[2] + y[0] * y[2]),
3. * (x[2] * x[1] + y[2] * y[1]) + 3. * (x[0] * x[3] + y[0] * y[3]),
4. * (x[3] * x[1] + y[3] * y[1]) + 2. * (x[2] * x[2] + y[2] * y[2]),
5. * (x[3] * x[2] + y[3] * y[2]),
3. * (x[3] * x[3] + y[3] * y[3]),
]);
poly.roots_between(0., 1., 1e-8)
}
/// Find the `t`-value(s) such that the tangent(s) at `t` pass through the given point.
pub fn pathseg_tangents_to_point(segment: PathSeg, point: Point) -> Vec<f64> {
segment.to_cubic().tangents_to_point(point).to_vec()
}
/// Return the subsegment for the given [TValue] range. Returns None if parametric value of `t1` is greater than `t2`.
pub fn trim_pathseg(segment: PathSeg, t1: TValue, t2: TValue) -> Option<PathSeg> {
let t1 = eval_pathseg(segment, t1);
@@ -202,6 +280,68 @@ pub fn eval_pathseg(segment: PathSeg, t_value: TValue) -> f64 {
}
}
/// Return an approximation of the length centroid, together with the length, of the bezier curve.
///
/// The length centroid is the center of mass for the arc length of the Bezier segment.
/// An infinitely thin wire forming the Bezier segment's shape would balance at this point.
///
/// - `accuracy` is used to approximate the curve.
pub(crate) fn pathseg_length_centroid_and_length(segment: PathSeg, accuracy: Option<f64>) -> (Vec2, f64) {
match segment {
PathSeg::Line(line) => ((line.start().to_vec2() + line.end().to_vec2()) / 2., (line.start().to_vec2() - line.end().to_vec2()).length()),
PathSeg::Quad(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
fn recurse(a0: Vec2, a1: Vec2, a2: Vec2, accuracy: f64, level: u8) -> (f64, Vec2) {
let lower = (a2 - a1).length();
let upper = (a1 - a0).length() + (a2 - a1).length();
if upper - lower <= 2. * accuracy || level >= 8 {
let length = (lower + upper) / 2.;
return (length, length * (a0 + a1 + a2) / 3.);
}
let b1 = 0.5 * (a0 + a1);
let c1 = 0.5 * (a1 + a2);
let b2 = 0.5 * (b1 + c1);
let (length1, centroid_part1) = recurse(a0, b1, b2, 0.5 * accuracy, level + 1);
let (length2, centroid_part2) = recurse(b2, c1, a2, 0.5 * accuracy, level + 1);
(length1 + length2, centroid_part1 + centroid_part2)
}
let (length, centroid_parts) = recurse(p0.to_vec2(), p1.to_vec2(), p2.to_vec2(), accuracy.unwrap_or_default(), 0);
(centroid_parts / length, length)
}
PathSeg::Cubic(cubic_bez) => {
let CubicBez { p0, p1, p2, p3 } = cubic_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, a3: Vec2, accuracy: f64, level: u8) -> (f64, Vec2) {
let lower = (a3 - a0).length();
let upper = (a1 - a0).length() + (a2 - a1).length() + (a3 - a2).length();
if upper - lower <= 2. * accuracy || level >= 8 {
let length = (lower + upper) / 2.;
return (length, length * (a0 + a1 + a2 + a3) / 4.);
}
let b1 = 0.5 * (a0 + a1);
let t0 = 0.5 * (a1 + a2);
let c1 = 0.5 * (a2 + a3);
let b2 = 0.5 * (b1 + t0);
let c2 = 0.5 * (t0 + c1);
let b3 = 0.5 * (b2 + c2);
let (length1, centroid_part1) = recurse(a0, b1, b2, b3, 0.5 * accuracy, level + 1);
let (length2, centroid_part2) = recurse(b3, c2, c1, a3, 0.5 * accuracy, level + 1);
(length1 + length2, centroid_part1 + centroid_part2)
}
let (length, centroid_parts) = recurse(p0.to_vec2(), p1.to_vec2(), p2.to_vec2(), p3.to_vec2(), accuracy.unwrap_or_default(), 0);
(centroid_parts / length, length)
}
}
}
/// Finds the t value of point on the given path segment i.e fractional distance along the segment's total length.
/// It uses a binary search to find the value `t` such that the ratio `length_up_to_t / total_length` approximates the input `distance`.
pub fn eval_pathseg_euclidean(segment: PathSeg, distance: f64, accuracy: f64) -> f64 {
@@ -392,8 +532,8 @@ pub fn miter_line_join(bezpath1: &BezPath, bezpath2: &BezPath, miter_limit: Opti
let in_segment = bezpath1.segments().last()?;
let out_segment = bezpath2.segments().next()?;
let in_tangent = segment_tangent(in_segment, 1.);
let out_tangent = segment_tangent(out_segment, 0.);
let in_tangent = pathseg_tangent(in_segment, 1.);
let out_tangent = pathseg_tangent(out_segment, 0.);
if in_tangent == DVec2::ZERO || out_tangent == DVec2::ZERO {
// Avoid panic from normalizing zero vectors
@@ -454,7 +594,7 @@ pub fn round_line_join(bezpath1: &BezPath, bezpath2: &BezPath, center: DVec2) ->
let center_to_left = left - center;
let in_segment = bezpath1.segments().last();
let in_tangent = in_segment.map(|in_segment| segment_tangent(in_segment, 1.));
let in_tangent = in_segment.map(|in_segment| pathseg_tangent(in_segment, 1.));
let mut angle = center_to_right.angle_to(center_to_left) / 2.;
let mut arc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right);

View File

@@ -105,8 +105,8 @@ mod test {
use super::*;
use crate::Node;
use crate::extract_xy::{ExtractXyNode, XY};
use crate::subpath::Subpath;
use crate::vector::Vector;
use bezier_rs::Subpath;
use glam::DVec2;
use std::pin::Pin;

View File

@@ -45,12 +45,26 @@ pub fn segment_intersections(segment1: PathSeg, segment2: PathSeg, accuracy: Opt
}
}
pub fn subsegment_intersections(segment1: PathSeg, min_t1: f64, max_t1: f64, segment2: PathSeg, min_t2: f64, max_t2: f64, accuracy: Option<f64>) -> Vec<(f64, f64)> {
let accuracy = accuracy.unwrap_or(DEFAULT_ACCURACY);
match (segment1, segment2) {
(PathSeg::Line(line), segment2) => segment2.intersect_line(line).iter().map(|i| (i.line_t, i.segment_t)).collect(),
(segment1, PathSeg::Line(line)) => segment1.intersect_line(line).iter().map(|i| (i.segment_t, i.line_t)).collect(),
(segment1, segment2) => {
let mut intersections = Vec::new();
segment_intersections_inner(segment1, min_t1, max_t1, segment2, min_t2, max_t2, accuracy, &mut intersections);
intersections
}
}
}
/// Implements [https://pomax.github.io/bezierinfo/#curveintersection] to find intersection between two Bezier segments
/// by splitting the segment recursively until the size of the subsegment's bounding box is smaller than the accuracy.
#[allow(clippy::too_many_arguments)]
fn segment_intersections_inner(segment1: PathSeg, min_t1: f64, max_t1: f64, segment2: PathSeg, min_t2: f64, max_t2: f64, accuracy: f64, intersections: &mut Vec<(f64, f64)>) {
let bbox1 = segment1.bounding_box();
let bbox2 = segment2.bounding_box();
let bbox1 = segment1.subsegment(min_t1..max_t1).bounding_box();
let bbox2 = segment2.subsegment(min_t2..max_t2).bounding_box();
let mid_t1 = (min_t1 + max_t1) / 2.;
let mid_t2 = (min_t2 + max_t2) / 2.;
@@ -58,7 +72,7 @@ fn segment_intersections_inner(segment1: PathSeg, min_t1: f64, max_t1: f64, segm
// Check if the bounding boxes overlap
if bbox1.overlaps(bbox2) {
// If bounding boxes overlap and they are small enough, we have found an intersection
if bbox1.width() < accuracy && bbox1.height() < accuracy && bbox2.width() < accuracy && bbox2.height() < accuracy {
if bbox1.width().abs() < accuracy && bbox1.height().abs() < accuracy && bbox2.width().abs() < accuracy && bbox2.height().abs() < accuracy {
// Use the middle `t` value, append the corresponding `t` value
intersections.push((mid_t1, mid_t2));
return;
@@ -125,6 +139,66 @@ pub fn filtered_all_segment_intersections(segment1: PathSeg, segment2: PathSeg,
})
}
/// Helper function to compute intersections between lists of subcurves.
/// This function uses the algorithm implemented in `intersections_between_subcurves`.
fn intersections_between_vectors_of_path_segments(subcurves1: &[(f64, f64, PathSeg)], subcurves2: &[(f64, f64, PathSeg)], accuracy: Option<f64>) -> Vec<(f64, f64)> {
let segment_pairs = subcurves1.iter().flat_map(move |(t11, t12, curve1)| {
subcurves2
.iter()
.filter_map(move |(t21, t22, curve2)| curve1.bounding_box().overlaps(curve2.bounding_box()).then_some((t11, t12, curve1, t21, t22, curve2)))
});
segment_pairs
.flat_map(|(&t11, &t12, &curve1, &t21, &t22, &curve2)| subsegment_intersections(curve1, t11, t12, curve2, t21, t22, accuracy))
.collect::<Vec<(f64, f64)>>()
}
fn pathseg_self_intersection(segment: PathSeg, accuracy: Option<f64>) -> Vec<(f64, f64)> {
let cubic_bez = match segment {
PathSeg::Line(_) | PathSeg::Quad(_) => return vec![],
PathSeg::Cubic(cubic_bez) => cubic_bez,
};
// Get 2 copies of the reduced curves
let quads1 = cubic_bez.to_quads(DEFAULT_ACCURACY).map(|(t1, t2, quad_bez)| (t1, t2, PathSeg::Quad(quad_bez))).collect::<Vec<_>>();
let quads2 = quads1.clone();
let num_curves = quads1.len();
// Adjacent reduced curves cannot intersect
if num_curves <= 2 {
return vec![];
}
// For each curve, look for intersections with every curve that is at least 2 indices away
quads1
.iter()
.take(num_curves - 2)
.enumerate()
.flat_map(|(index, &subsegment)| intersections_between_vectors_of_path_segments(&[subsegment], &quads2[index + 2..], accuracy))
.collect()
}
/// Returns a list of parametric `t` values that correspond to the self intersection points of the current bezier curve. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point.
/// If the difference between 2 adjacent `t` values is less than the minimum difference, the filtering takes the larger `t` value and discards the smaller `t` value.
/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.
/// - `minimum_separation` - The minimum difference between adjacent `t` values in sorted order
pub fn pathseg_self_intersections(segment: PathSeg, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(f64, f64)> {
let mut intersection_t_values = pathseg_self_intersection(segment, accuracy);
intersection_t_values.sort_by(|a, b| (a.0 + a.1).partial_cmp(&(b.0 + b.1)).unwrap());
intersection_t_values.iter().fold(Vec::new(), |mut accumulator, t| {
if !accumulator.is_empty()
&& (accumulator.last().unwrap().0 - t.0).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)
&& (accumulator.last().unwrap().1 - t.1).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)
{
accumulator.pop();
}
accumulator.push(*t);
accumulator
})
}
#[cfg(test)]
mod tests {
use super::{bezpath_and_segment_intersections, filtered_segment_intersections};

View File

@@ -1,7 +1,7 @@
use glam::DVec2;
use kurbo::{ParamCurve, ParamCurveDeriv, PathSeg};
pub fn segment_tangent(segment: PathSeg, t: f64) -> DVec2 {
pub fn pathseg_tangent(segment: PathSeg, t: f64) -> DVec2 {
// NOTE: .deriv() method gives inaccurate result when it is 1.
let t = if t == 1. { 1. - f64::EPSILON } else { t };

View File

@@ -1,8 +1,12 @@
use super::algorithms::intersection::filtered_segment_intersections;
use super::misc::dvec2_to_point;
use crate::math::math_ext::QuadExt;
use crate::math::quad::Quad;
use crate::subpath::Subpath;
use crate::vector::PointId;
use bezier_rs::Subpath;
use crate::vector::misc::point_to_dvec2;
use glam::{DAffine2, DMat2, DVec2};
use kurbo::{Affine, ParamCurve, PathSeg, Point, Shape};
#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FreePoint {
@@ -99,7 +103,7 @@ impl ClickTarget {
}
/// Does the click target intersect the path
pub fn intersect_path<It: Iterator<Item = bezier_rs::Bezier>>(&self, mut bezier_iter: impl FnMut() -> It, layer_transform: DAffine2) -> bool {
pub fn intersect_path<It: Iterator<Item = PathSeg>>(&self, mut bezier_iter: impl FnMut() -> It, layer_transform: DAffine2) -> bool {
// Check if the matrix is not invertible
let mut layer_transform = layer_transform;
if layer_transform.matrix2.determinant().abs() <= f64::EPSILON {
@@ -107,25 +111,25 @@ impl ClickTarget {
}
let inverse = layer_transform.inverse();
let mut bezier_iter = || bezier_iter().map(|bezier| bezier.apply_transformation(|point| inverse.transform_point2(point)));
let mut bezier_iter = || bezier_iter().map(|bezier| Affine::new(inverse.to_cols_array()) * bezier);
match self.target_type() {
ClickTargetType::Subpath(subpath) => {
// Check if outlines intersect
let outline_intersects = |path_segment: bezier_rs::Bezier| bezier_iter().any(|line| !path_segment.intersections(&line, None, None).is_empty());
let outline_intersects = |path_segment: PathSeg| bezier_iter().any(|line| !filtered_segment_intersections(path_segment, line, None, None).is_empty());
if subpath.iter().any(outline_intersects) {
return true;
}
// Check if selection is entirely within the shape
if subpath.closed() && bezier_iter().next().is_some_and(|bezier| subpath.contains_point(bezier.start)) {
if subpath.closed() && bezier_iter().next().is_some_and(|bezier| subpath.contains_point(point_to_dvec2(bezier.start()))) {
return true;
}
// Check if shape is entirely within selection
let any_point_from_subpath = subpath.manipulator_groups().first().map(|manipulators| manipulators.anchor);
any_point_from_subpath.is_some_and(|shape_point| bezier_iter().map(|bezier| bezier.winding(shape_point)).sum::<i32>() != 0)
any_point_from_subpath.is_some_and(|shape_point| bezier_iter().map(|bezier| bezier.winding(Point::new(shape_point.x, shape_point.y))).sum::<i32>() != 0)
}
ClickTargetType::FreePoint(point) => bezier_iter().map(|bezier: bezier_rs::Bezier| bezier.winding(point.position)).sum::<i32>() != 0,
ClickTargetType::FreePoint(point) => bezier_iter().map(|bezier: PathSeg| bezier.winding(dvec2_to_point(point.position))).sum::<i32>() != 0,
}
}
@@ -144,7 +148,7 @@ impl ClickTarget {
// Allows for selecting lines
// TODO: actual intersection of stroke
let inflated_quad = Quad::from_box(target_bounds);
self.intersect_path(|| inflated_quad.bezier_lines(), layer_transform)
self.intersect_path(|| inflated_quad.to_lines(), layer_transform)
}
/// Does the click target intersect the point (not accounting for stroke size)

View File

@@ -2,10 +2,10 @@ use super::misc::{ArcType, AsU64, GridType};
use super::{PointId, SegmentId, StrokeId};
use crate::Ctx;
use crate::registry::types::{Angle, PixelSize};
use crate::subpath;
use crate::table::Table;
use crate::vector::Vector;
use crate::vector::misc::HandleId;
use bezier_rs::Subpath;
use glam::DVec2;
trait CornerRadius {
@@ -14,7 +14,7 @@ trait CornerRadius {
impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
Table::new_from_element(Vector::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
}
}
impl CornerRadius for [f64; 4] {
@@ -34,7 +34,7 @@ impl CornerRadius for [f64; 4] {
} else {
self
};
Table::new_from_element(Vector::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
}
}
@@ -47,7 +47,7 @@ fn circle(
radius: f64,
) -> Table<Vector> {
let radius = radius.abs();
Table::new_from_element(Vector::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
}
#[node_macro::node(category("Vector: Shape"))]
@@ -63,14 +63,14 @@ fn arc(
sweep_angle: Angle,
arc_type: ArcType,
) -> Table<Vector> {
Table::new_from_element(Vector::from_subpath(Subpath::new_arc(
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc(
radius,
start_angle / 360. * std::f64::consts::TAU,
sweep_angle / 360. * std::f64::consts::TAU,
match arc_type {
ArcType::Open => bezier_rs::ArcType::Open,
ArcType::Closed => bezier_rs::ArcType::Closed,
ArcType::PieSlice => bezier_rs::ArcType::PieSlice,
ArcType::Open => subpath::ArcType::Open,
ArcType::Closed => subpath::ArcType::Closed,
ArcType::PieSlice => subpath::ArcType::PieSlice,
},
)))
}
@@ -90,7 +90,7 @@ fn ellipse(
let corner1 = -radius;
let corner2 = radius;
let mut ellipse = Vector::from_subpath(Subpath::new_ellipse(corner1, corner2));
let mut ellipse = Vector::from_subpath(subpath::Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
@@ -133,7 +133,7 @@ fn regular_polygon<T: AsU64>(
) -> Table<Vector> {
let points = sides.as_u64();
let radius: f64 = radius * 2.;
Table::new_from_element(Vector::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
}
#[node_macro::node(category("Vector: Shape"))]
@@ -155,12 +155,12 @@ fn star<T: AsU64>(
let diameter: f64 = radius_1 * 2.;
let inner_diameter = radius_2 * 2.;
Table::new_from_element(Vector::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
}
#[node_macro::node(category("Vector: Shape"))]
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<Vector> {
Table::new_from_element(Vector::from_subpath(Subpath::new_line(start, end)))
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(start, end)))
}
trait GridSpacing {
@@ -212,7 +212,7 @@ fn grid<T: GridSpacing>(
if let Some(other_index) = to_index {
vector
.segment_domain
.push(segment_id.next_id(), other_index, current_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
.push(segment_id.next_id(), other_index, current_index, subpath::BezierHandles::Linear, StrokeId::ZERO);
}
};
@@ -249,7 +249,7 @@ fn grid<T: GridSpacing>(
if let Some(other_index) = to_index {
vector
.segment_domain
.push(segment_id.next_id(), other_index, current_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
.push(segment_id.next_id(), other_index, current_index, subpath::BezierHandles::Linear, StrokeId::ZERO);
}
};
@@ -289,7 +289,7 @@ mod tests {
assert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
assert_eq!(bezier.handles, subpath::BezierHandles::Linear);
assert!(
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
"Length of {} should be 10",
@@ -304,7 +304,7 @@ mod tests {
assert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
assert_eq!(bezier.handles, subpath::BezierHandles::Linear);
let vector = bezier.start - bezier.end;
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;
assert!([90., 150., 40.].into_iter().any(|target| (target - angle).abs() < 1e-10), "unexpected angle of {}", angle)

View File

@@ -1,7 +1,7 @@
use super::PointId;
use super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
use crate::subpath::{BezierHandles, ManipulatorGroup};
use crate::vector::{SegmentId, Vector};
use bezier_rs::{BezierHandles, ManipulatorGroup};
use dyn_any::DynAny;
use glam::DVec2;
use kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez};
@@ -115,18 +115,18 @@ pub fn segment_to_handles(segment: &PathSeg) -> BezierHandles {
pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> PathSeg {
match handles {
bezier_rs::BezierHandles::Linear => {
BezierHandles::Linear => {
let p0 = dvec2_to_point(start);
let p1 = dvec2_to_point(end);
PathSeg::Line(Line::new(p0, p1))
}
bezier_rs::BezierHandles::Quadratic { handle } => {
BezierHandles::Quadratic { handle } => {
let p0 = dvec2_to_point(start);
let p1 = dvec2_to_point(handle);
let p2 = dvec2_to_point(end);
PathSeg::Quad(QuadBez::new(p0, p1, p2))
}
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
BezierHandles::Cubic { handle_start, handle_end } => {
let p0 = dvec2_to_point(start);
let p1 = dvec2_to_point(handle_start);
let p2 = dvec2_to_point(handle_end);
@@ -211,8 +211,8 @@ pub fn is_linear(segment: PathSeg) -> bool {
}
}
/// Get an iterator over the coordinates of all points in a path segment.
pub fn get_segment_points(segment: PathSeg) -> Vec<Point> {
/// Get an vec of all the points in a path segment.
pub fn pathseg_points_vec(segment: PathSeg) -> Vec<Point> {
match segment {
PathSeg::Line(line) => [line.p0, line.p1].to_vec(),
PathSeg::Quad(quad_bez) => [quad_bez.p0, quad_bez.p1, quad_bez.p2].to_vec(),
@@ -225,8 +225,8 @@ pub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> b
let seg1 = if is_linear(seg1) { PathSeg::Line(Line::new(seg1.start(), seg1.end())) } else { seg1 };
let seg2 = if is_linear(seg2) { PathSeg::Line(Line::new(seg2.start(), seg2.end())) } else { seg2 };
let seg1_points = get_segment_points(seg1);
let seg2_points = get_segment_points(seg2);
let seg1_points = pathseg_points_vec(seg1);
let seg2_points = pathseg_points_vec(seg2);
let cmp = |a: f64, b: f64| a.sub(b).abs() < max_abs_diff;

View File

@@ -9,7 +9,6 @@ mod vector_modification;
mod vector_nodes;
mod vector_types;
pub use bezier_rs;
pub use reference_point::*;
pub use style::PathStyle;
pub use vector_nodes::*;

View File

@@ -1,6 +1,6 @@
use crate::subpath::{Bezier, BezierHandles, Identifier, ManipulatorGroup, Subpath};
use crate::vector::misc::{HandleId, dvec2_to_point};
use crate::vector::vector_types::Vector;
use bezier_rs::{BezierHandles, ManipulatorGroup};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use kurbo::{CubicBez, Line, PathSeg, QuadBez};
@@ -442,11 +442,7 @@ impl SegmentDomain {
zip(ids, zip(start_point, zip(end_point, handles))).map(|(id, (start_point, (end_point, handles)))| (id, start_point, end_point, handles))
}
pub(crate) fn pair_handles_and_points_mut_by_index(
&mut self,
index1: usize,
index2: usize,
) -> (&mut bezier_rs::BezierHandles, &mut usize, &mut usize, &mut bezier_rs::BezierHandles, &mut usize, &mut usize) {
pub(crate) fn pair_handles_and_points_mut_by_index(&mut self, index1: usize, index2: usize) -> (&mut BezierHandles, &mut usize, &mut usize, &mut BezierHandles, &mut usize, &mut usize) {
// Use split_at_mut to avoid multiple mutable borrows of the same slice
let (handles_first, handles_second) = self.handles.split_at_mut(index2.max(index1));
let (start_first, start_second) = self.start_point.split_at_mut(index2.max(index1));
@@ -686,25 +682,25 @@ impl Vector {
}
}
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.
fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: BezierHandles) -> bezier_rs::Bezier {
/// Construct a [`Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.
fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: BezierHandles) -> Bezier {
let start = self.point_domain.positions()[start];
let end = self.point_domain.positions()[end];
bezier_rs::Bezier { start, end, handles }
Bezier { start, end, handles }
}
/// Tries to convert a segment with the specified id to a [`bezier_rs::Bezier`], returning None if the id is invalid.
pub fn segment_from_id(&self, id: SegmentId) -> Option<bezier_rs::Bezier> {
/// Tries to convert a segment with the specified id to a [`Bezier`], returning None if the id is invalid.
pub fn segment_from_id(&self, id: SegmentId) -> Option<Bezier> {
self.segment_points_from_id(id).map(|(_, _, bezier)| bezier)
}
/// Tries to convert a segment with the specified id to the start and end points and a [`bezier_rs::Bezier`], returning None if the id is invalid.
pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, bezier_rs::Bezier)> {
/// Tries to convert a segment with the specified id to the start and end points and a [`Bezier`], returning None if the id is invalid.
pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, Bezier)> {
Some(self.segment_points_from_index(self.segment_domain.id_to_index(id)?))
}
/// Tries to convert a segment with the specified index to the start and end points and a [`bezier_rs::Bezier`].
pub fn segment_points_from_index(&self, index: usize) -> (PointId, PointId, bezier_rs::Bezier) {
/// Tries to convert a segment with the specified index to the start and end points and a [`Bezier`].
pub fn segment_points_from_index(&self, index: usize) -> (PointId, PointId, Bezier) {
let start = self.segment_domain.start_point[index];
let end = self.segment_domain.end_point[index];
let start_id = self.point_domain.ids()[start];
@@ -712,7 +708,7 @@ impl Vector {
(start_id, end_id, self.segment_to_bezier_with_index(start, end, self.segment_domain.handles[index]))
}
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
/// Iterator over all of the [`Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
pub fn segment_iter(&self) -> impl Iterator<Item = (SegmentId, PathSeg, PointId, PointId)> {
let to_segment = |(((&handles, &id), &start), &end)| (id, self.path_segment_from_index(start, end, handles), self.point_domain.ids()[start], self.point_domain.ids()[end]);
@@ -725,8 +721,8 @@ impl Vector {
.map(to_segment)
}
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
pub fn segment_bezier_iter(&self) -> impl Iterator<Item = (SegmentId, bezier_rs::Bezier, PointId, PointId)> + '_ {
/// Iterator over all of the [`Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
pub fn segment_bezier_iter(&self) -> impl Iterator<Item = (SegmentId, Bezier, PointId, PointId)> + '_ {
let to_bezier = |(((&handles, &id), &start), &end)| (id, self.segment_to_bezier_with_index(start, end, handles), self.point_domain.ids()[start], self.point_domain.ids()[end]);
self.segment_domain
.handles
@@ -808,8 +804,8 @@ impl Vector {
}
}
/// Construct a [`bezier_rs::Bezier`] curve from an iterator of segments with (handles, start point, end point) independently of discontinuities.
pub fn subpath_from_segments_ignore_discontinuities(&self, segments: impl Iterator<Item = (BezierHandles, usize, usize)>) -> Option<bezier_rs::Subpath<PointId>> {
/// Construct a [`Bezier`] curve from an iterator of segments with (handles, start point, end point) independently of discontinuities.
pub fn subpath_from_segments_ignore_discontinuities(&self, segments: impl Iterator<Item = (BezierHandles, usize, usize)>) -> Option<Subpath<PointId>> {
let mut first_point = None;
let mut manipulators_list = Vec::new();
let mut last: Option<(usize, BezierHandles)> = None;
@@ -842,10 +838,10 @@ impl Vector {
}
}
Some(bezier_rs::Subpath::new(manipulators_list, closed))
Some(Subpath::new(manipulators_list, closed))
}
/// Construct a [`bezier_rs::Bezier`] curve for each region, skipping invalid regions.
/// Construct a [`Bezier`] curve for each region, skipping invalid regions.
pub fn region_manipulator_groups(&self) -> impl Iterator<Item = (RegionId, Vec<ManipulatorGroup<PointId>>)> + '_ {
self.region_domain
.id
@@ -903,12 +899,12 @@ impl Vector {
}
}
/// Construct a [`bezier_rs::Bezier`] curve for stroke.
pub fn stroke_bezier_paths(&self) -> impl Iterator<Item = bezier_rs::Subpath<PointId>> {
self.build_stroke_path_iter().map(|(manipulators_list, closed)| bezier_rs::Subpath::new(manipulators_list, closed))
/// Construct a [`Bezier`] curve for stroke.
pub fn stroke_bezier_paths(&self) -> impl Iterator<Item = Subpath<PointId>> {
self.build_stroke_path_iter().map(|(manipulators_list, closed)| Subpath::new(manipulators_list, closed))
}
/// Construct and return an iterator of Vec of `(bezier_rs::ManipulatorGroup<PointId>], bool)` for stroke.
/// Construct and return an iterator of Vec of `(ManipulatorGroup<PointId>], bool)` for stroke.
/// The boolean in the tuple indicates if the path is closed.
pub fn stroke_manipulator_groups(&self) -> impl Iterator<Item = (Vec<ManipulatorGroup<PointId>>, bool)> {
self.build_stroke_path_iter()
@@ -1094,7 +1090,7 @@ impl Iterator for StrokePathIter<'_> {
}
}
impl bezier_rs::Identifier for PointId {
impl Identifier for PointId {
fn new() -> Self {
Self::generate()
}

View File

@@ -1,9 +1,9 @@
use super::*;
use crate::Ctx;
use crate::subpath::BezierHandles;
use crate::table::{Table, TableRow};
use crate::uuid::{NodeId, generate_uuid};
use crate::vector::misc::{HandleId, HandleType, point_to_dvec2};
use bezier_rs::BezierHandles;
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use kurbo::{BezPath, PathEl, Point};
@@ -684,14 +684,15 @@ impl HandleExt for HandleId {
#[cfg(test)]
mod tests {
use kurbo::{PathSeg, QuadBez};
use super::*;
use crate::subpath::{Bezier, Subpath};
#[test]
fn modify_new() {
let vector = Vector::from_subpaths(
[bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), bezier_rs::Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)],
false,
);
let vector = Vector::from_subpaths([Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)], false);
let modify = VectorModification::create_from_vector(&vector);
@@ -702,14 +703,13 @@ mod tests {
#[test]
fn modify_existing() {
use bezier_rs::{Bezier, Subpath};
let subpaths = [
Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE),
Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO),
Subpath::from_beziers(
&[
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(10., 0.)),
Bezier::from_quadratic_dvec2(DVec2::new(10., 0.), DVec2::new(15., 10.), DVec2::new(20., 0.)),
PathSeg::Quad(QuadBez::new(Point::new(0., 0.), Point::new(5., 10.), Point::new(10., 0.))),
PathSeg::Quad(QuadBez::new(Point::new(10., 0.), Point::new(15., 10.), Point::new(20., 0.))),
],
false,
),

View File

@@ -7,6 +7,7 @@ use super::{PointId, SegmentDomain, SegmentId, StrokeId, Vector, VectorExt};
use crate::bounds::{BoundingBox, RenderBoundingBox};
use crate::raster_types::{CPU, GPU, Raster};
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
use crate::subpath::{BezierHandles, ManipulatorGroup};
use crate::table::{Table, TableRow, TableRowMut};
use crate::transform::{Footprint, ReferencePoint, Transform};
use crate::vector::PointDomain;
@@ -17,7 +18,6 @@ use crate::vector::misc::{handles_to_segment, segment_to_handles};
use crate::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use crate::vector::{FillId, RegionId};
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
use bezier_rs::ManipulatorGroup;
use core::f64::consts::PI;
use core::hash::{Hash, Hasher};
use glam::{DAffine2, DVec2};
@@ -827,11 +827,11 @@ async fn points_to_polyline(_: impl Ctx, mut points: Table<Vector>, #[default(tr
if points_count > 2 {
(0..points_count - 1).for_each(|i| {
segment_domain.push(SegmentId::generate(), i, i + 1, bezier_rs::BezierHandles::Linear, StrokeId::generate());
segment_domain.push(SegmentId::generate(), i, i + 1, BezierHandles::Linear, StrokeId::generate());
});
if closed {
segment_domain.push(SegmentId::generate(), points_count - 1, 0, bezier_rs::BezierHandles::Linear, StrokeId::generate());
segment_domain.push(SegmentId::generate(), points_count - 1, 0, BezierHandles::Linear, StrokeId::generate());
row.element
.region_domain
@@ -1351,7 +1351,7 @@ async fn spline(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {
let handle_start = first_handles[i];
let handle_end = positions[next_index] * 2. - first_handles[next_index];
let handles = bezier_rs::BezierHandles::Cubic { handle_start, handle_end };
let handles = BezierHandles::Cubic { handle_start, handle_end };
segment_domain.push(SegmentId::generate(), start_index, end_index, handles, stroke_id);
}
@@ -1405,14 +1405,14 @@ async fn jitter_points(
}
match handles {
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
BezierHandles::Cubic { handle_start, handle_end } => {
*handle_start += start_delta;
*handle_end += end_delta;
}
bezier_rs::BezierHandles::Quadratic { handle } => {
BezierHandles::Quadratic { handle } => {
*handle = row.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;
}
bezier_rs::BezierHandles::Linear => {}
BezierHandles::Linear => {}
}
}
@@ -1846,7 +1846,7 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
let mut next_id = vector.segment_domain.next_id();
for &[start, end] in new_segments {
let handles = bezier_rs::BezierHandles::Linear;
let handles = BezierHandles::Linear;
vector.segment_domain.push(next_id.next_id(), start, end, handles, StrokeId::ZERO);
}
}

View File

@@ -4,12 +4,12 @@ pub use super::vector_attributes::*;
pub use super::vector_modification::*;
use crate::bounds::{BoundingBox, RenderBoundingBox};
use crate::math::quad::Quad;
use crate::subpath::{BezierHandles, ManipulatorGroup, Subpath};
use crate::table::Table;
use crate::transform::Transform;
use crate::vector::click_target::{ClickTargetType, FreePoint};
use crate::vector::misc::{HandleId, ManipulatorPointId};
use crate::{AlphaBlending, Color, Graphic};
use bezier_rs::{BezierHandles, ManipulatorGroup};
use core::borrow::Borrow;
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
@@ -60,15 +60,15 @@ impl std::hash::Hash for Vector {
impl Vector {
/// Add a Bezier-rs subpath to this path.
pub fn append_subpath(&mut self, subpath: impl Borrow<bezier_rs::Subpath<PointId>>, preserve_id: bool) {
let subpath: &bezier_rs::Subpath<PointId> = subpath.borrow();
pub fn append_subpath(&mut self, subpath: impl Borrow<Subpath<PointId>>, preserve_id: bool) {
let subpath: &Subpath<PointId> = subpath.borrow();
let stroke_id = StrokeId::ZERO;
let mut point_id = self.point_domain.next_id();
let handles = |a: &ManipulatorGroup<_>, b: &ManipulatorGroup<_>| match (a.out_handle, b.in_handle) {
(None, None) => bezier_rs::BezierHandles::Linear,
(Some(handle), None) | (None, Some(handle)) => bezier_rs::BezierHandles::Quadratic { handle },
(Some(handle_start), Some(handle_end)) => bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
(None, None) => BezierHandles::Linear,
(Some(handle), None) | (None, Some(handle)) => BezierHandles::Quadratic { handle },
(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic { handle_start, handle_end },
};
let [mut first_seg, mut last_seg] = [None, None];
let mut segment_id = self.segment_domain.next_id();
@@ -132,7 +132,7 @@ impl Vector {
}
/// Construct some new vector path from a single Bezier-rs subpath with an identity transform and black fill.
pub fn from_subpath(subpath: impl Borrow<bezier_rs::Subpath<PointId>>) -> Self {
pub fn from_subpath(subpath: impl Borrow<Subpath<PointId>>) -> Self {
Self::from_subpaths([subpath], false)
}
@@ -144,7 +144,7 @@ impl Vector {
}
/// Construct some new vector path from Bezier-rs subpaths with an identity transform and black fill.
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<bezier_rs::Subpath<PointId>>>, preserve_id: bool) -> Self {
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<Subpath<PointId>>>, preserve_id: bool) -> Self {
let mut vector = Self::default();
for subpath in subpaths.into_iter() {
@@ -185,7 +185,7 @@ impl Vector {
for (start, end) in segments_to_add {
let segment_id = self.segment_domain.next_id().next_id();
self.segment_domain.push(segment_id, start, end, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
self.segment_domain.push(segment_id, start, end, BezierHandles::Linear, StrokeId::ZERO);
}
}
@@ -244,14 +244,19 @@ impl Vector {
self.segment_domain.end_point().iter().map(|&index| self.point_domain.ids()[index])
}
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: (Option<DVec2>, Option<DVec2>), stroke: StrokeId) {
let [Some(start), Some(end)] = [start, end].map(|id| self.point_domain.resolve_id(id)) else {
return;
};
let handles = match handles {
(None, None) => BezierHandles::Linear,
(None, Some(handle)) | (Some(handle), None) => BezierHandles::Quadratic { handle },
(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic { handle_start, handle_end },
};
self.segment_domain.push(id, start, end, handles, stroke)
}
pub fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut bezier_rs::BezierHandles, PointId, PointId)> {
pub fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut BezierHandles, PointId, PointId)> {
self.segment_domain
.handles_mut()
.map(|(id, handles, start, end)| (id, handles, self.point_domain.ids()[start], self.point_domain.ids()[end]))
@@ -515,9 +520,11 @@ pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resu
#[cfg(test)]
mod tests {
use kurbo::{CubicBez, PathSeg, Point};
use super::*;
fn assert_subpath_eq(generated: &[bezier_rs::Subpath<PointId>], expected: &[bezier_rs::Subpath<PointId>]) {
fn assert_subpath_eq(generated: &[Subpath<PointId>], expected: &[Subpath<PointId>]) {
assert_eq!(generated.len(), expected.len());
for (generated, expected) in generated.iter().zip(expected) {
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());
@@ -532,10 +539,10 @@ mod tests {
#[test]
fn construct_closed_subpath() {
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let vector = Vector::from_subpath(&circle);
assert_eq!(vector.point_domain.ids().len(), 4);
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
assert_eq!(bezier_paths.len(), 4);
assert!(bezier_paths.iter().all(|&bezier| circle.iter().any(|original_bezier| original_bezier == bezier)));
@@ -545,11 +552,11 @@ mod tests {
#[test]
fn construct_open_subpath() {
let bezier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
let subpath = bezier_rs::Subpath::from_bezier(&bezier);
let bezier = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(-1., -1.), Point::new(1., 1.), Point::new(1., 0.)));
let subpath = Subpath::from_bezier(bezier);
let vector = Vector::from_subpath(&subpath);
assert_eq!(vector.point_domain.ids().len(), 2);
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
assert_eq!(bezier_paths, vec![bezier]);
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
@@ -558,14 +565,14 @@ mod tests {
#[test]
fn construct_many_subpath() {
let curve = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
let curve = bezier_rs::Subpath::from_bezier(&curve);
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let curve = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(-1., -1.), Point::new(1., 1.), Point::new(1., 0.)));
let curve = Subpath::from_bezier(curve);
let circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let vector = Vector::from_subpaths([&curve, &circle], false);
assert_eq!(vector.point_domain.ids().len(), 6);
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
assert_eq!(bezier_paths.len(), 5);
assert!(bezier_paths.iter().all(|&bezier| circle.iter().chain(curve.iter()).any(|original_bezier| original_bezier == bezier)));