Skip to content

Commit 950cc52

Browse files
committed
refactor tests
1 parent 967c8c5 commit 950cc52

4 files changed

Lines changed: 170 additions & 80 deletions

File tree

node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use std::path::Path;
2+
13
use super::poisson_disk::poisson_disk_sample;
2-
use crate::vector::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
34
use crate::vector::misc::{PointSpacingType, dvec2_to_point};
45
use glam::DVec2;
5-
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape};
6+
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, Rect, Shape};
67

78
/// Splits the [`BezPath`] at `t` value which lie in the range of [0, 1].
89
/// Returns [`None`] if the given [`BezPath`] has no segments or `t` is within f64::EPSILON of 0 or 1.
@@ -175,6 +176,25 @@ pub fn t_value_to_parametric(bezpath: &BezPath, t: f64, euclidian: bool, segment
175176
bezpath_t_value_to_parametric(bezpath, BezPathTValue::GlobalParametric(t), segments_length)
176177
}
177178

179+
pub enum TValue {
180+
Parametric(f64),
181+
Euclidean(f64),
182+
}
183+
184+
pub fn trim_pathseg(segment: PathSeg, t1: TValue, t2: TValue) -> Option<PathSeg> {
185+
let t1 = eval_pathseg(segment, t1);
186+
let t2 = eval_pathseg(segment, t2);
187+
188+
if t1 > t2 { None } else { Some(segment.subsegment(t1..t2)) }
189+
}
190+
191+
pub fn eval_pathseg(segment: PathSeg, t_value: TValue) -> f64 {
192+
match t_value {
193+
TValue::Parametric(t) => t,
194+
TValue::Euclidean(t) => eval_pathseg_euclidean(segment, t, DEFAULT_ACCURACY),
195+
}
196+
}
197+
178198
/// Finds the t value of point on the given path segment i.e fractional distance along the segment's total length.
179199
/// It uses a binary search to find the value `t` such that the ratio `length_up_to_t / total_length` approximates the input `distance`.
180200
pub fn eval_pathseg_euclidean(path_segment: PathSeg, distance: f64, accuracy: f64) -> f64 {
@@ -315,16 +335,3 @@ pub fn poisson_disk_points(bezpath_index: usize, bezpaths: &[(BezPath, Rect)], s
315335

316336
poisson_disk_sample(offset, width, height, separation_disk_diameter, point_in_shape_checker, line_intersect_shape_checker, rng)
317337
}
318-
319-
/// Returns true if the Bezier curve is equivalent to a line.
320-
///
321-
/// **NOTE**: 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.
322-
pub fn is_linear(segment: &PathSeg) -> bool {
323-
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 };
324-
325-
match *segment {
326-
PathSeg::Line(_) => true,
327-
PathSeg::Quad(QuadBez { p0, p1, p2 }) => is_colinear(p0, p1, p2),
328-
PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => is_colinear(p0, p1, p3) && is_colinear(p0, p2, p3),
329-
}
330-
}

node-graph/gcore/src/vector/misc.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
use std::ops::Sub;
2+
13
use bezier_rs::{BezierHandles, ManipulatorGroup, Subpath};
24
use dyn_any::DynAny;
35
use glam::DVec2;
4-
use kurbo::{BezPath, CubicBez, Line, PathSeg, Point, QuadBez};
6+
use kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez};
57

6-
use super::PointId;
8+
use super::{PointId, algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE};
79

810
/// Represents different ways of calculating the centroid.
911
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
@@ -197,3 +199,38 @@ pub fn bezpath_to_manipulator_groups(bezpath: &BezPath) -> (Vec<ManipulatorGroup
197199

198200
(manipulator_groups, is_closed)
199201
}
202+
203+
/// Returns true if the Bezier curve is equivalent to a line.
204+
///
205+
/// **NOTE**: 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.
206+
pub fn is_linear(segment: PathSeg) -> bool {
207+
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 };
208+
209+
match segment {
210+
PathSeg::Line(_) => true,
211+
PathSeg::Quad(QuadBez { p0, p1, p2 }) => is_colinear(p0, p1, p2),
212+
PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => is_colinear(p0, p1, p3) && is_colinear(p0, p2, p3),
213+
}
214+
}
215+
216+
/// Get an iterator over the coordinates of all points in a path segment.
217+
pub fn get_segment_points(segment: PathSeg) -> Vec<Point> {
218+
match segment {
219+
PathSeg::Line(line) => [line.p0, line.p1].to_vec(),
220+
PathSeg::Quad(quad_bez) => [quad_bez.p0, quad_bez.p1, quad_bez.p2].to_vec(),
221+
PathSeg::Cubic(cubic_bez) => [cubic_bez.p0, cubic_bez.p1, cubic_bez.p2, cubic_bez.p3].to_vec(),
222+
}
223+
}
224+
225+
/// Returns true if the corresponding points of the two [PathSeg]'s are within the provided absolute value difference from each other.
226+
pub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> bool {
227+
let seg1 = if is_linear(seg1) { PathSeg::Line(Line::new(seg1.start(), seg1.end())) } else { seg1 };
228+
let seg2 = if is_linear(seg2) { PathSeg::Line(Line::new(seg2.start(), seg2.end())) } else { seg2 };
229+
230+
let seg1_points = get_segment_points(seg1);
231+
let seg2_points = get_segment_points(seg2);
232+
233+
let cmp = |a: f64, b: f64| a.sub(b).abs() < max_abs_diff;
234+
235+
seg1_points.len() == seg2_points.len() && seg1_points.into_iter().zip(seg2_points).all(|(a, b)| cmp(a.x, b.x) && cmp(a.y, b.y))
236+
}

node-graph/gcore/src/vector/vector_data/attributes.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::vector::vector_data::{HandleId, VectorData};
33
use bezier_rs::{BezierHandles, ManipulatorGroup};
44
use dyn_any::DynAny;
55
use glam::{DAffine2, DVec2};
6+
use kurbo::{CubicBez, Line, PathSeg, QuadBez};
67
use std::collections::HashMap;
78
use std::hash::{Hash, Hasher};
89
use std::iter::zip;
@@ -673,6 +674,18 @@ impl FoundSubpath {
673674
}
674675

675676
impl VectorData {
677+
/// Construct a [`kurbo::PathSeg`] by resolving the points from their ids.
678+
fn path_segment_from_index(&self, start: usize, end: usize, handles: BezierHandles) -> PathSeg {
679+
let start = dvec2_to_point(self.point_domain.positions()[start]);
680+
let end = dvec2_to_point(self.point_domain.positions()[end]);
681+
682+
match handles {
683+
BezierHandles::Linear => PathSeg::Line(Line::new(start, end)),
684+
BezierHandles::Quadratic { handle } => PathSeg::Quad(QuadBez::new(start, dvec2_to_point(handle), end)),
685+
BezierHandles::Cubic { handle_start, handle_end } => PathSeg::Cubic(CubicBez::new(start, dvec2_to_point(handle_start), dvec2_to_point(handle_end), end)),
686+
}
687+
}
688+
676689
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.
677690
fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: BezierHandles) -> bezier_rs::Bezier {
678691
let start = self.point_domain.positions()[start];
@@ -699,6 +712,19 @@ impl VectorData {
699712
(start_id, end_id, self.segment_to_bezier_with_index(start, end, self.segment_domain.handles[index]))
700713
}
701714

715+
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
716+
pub fn segment_iter(&self) -> impl Iterator<Item = (SegmentId, PathSeg, PointId, PointId)> {
717+
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]);
718+
719+
self.segment_domain
720+
.handles
721+
.iter()
722+
.zip(&self.segment_domain.id)
723+
.zip(self.segment_domain.start_point())
724+
.zip(self.segment_domain.end_point())
725+
.map(to_segment)
726+
}
727+
702728
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
703729
pub fn segment_bezier_iter(&self) -> impl Iterator<Item = (SegmentId, bezier_rs::Bezier, PointId, PointId)> + '_ {
704730
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]);

0 commit comments

Comments
 (0)