Skip to content

Commit c5800aa

Browse files
MohdMohsin97Keavonindierusty
authored
Fix the Bevel node so it applies a consistent bevel size regardless of angle (#2293)
* feat: update the bevel algorithm for same bevel length * Fix: Resolves issue in bevel algorithm * fix bevel algorithm * Feat : update the bevel algorithm for bezier curve * Feat: Add support for rounded and inward-rounded Bevel options * fix bevel algo for curves * Nits * refactor area node * refactor close path node * small refactor of 'check_point_inside_shape' method * copy is_linear function from bezier-rs lib * refactor bevel node implementation * cleanup * refactor bevel implementation * fix transformation * cleanup * update the bevel algorithm * Code review * Clean up setup logic for failing test * update tests to reflect new algorithm * Decimal integer nits --------- Co-authored-by: Keavon Chambers <[email protected]> Co-authored-by: indierusty <[email protected]>
1 parent e89cded commit c5800aa

File tree

5 files changed

+329
-101
lines changed

5 files changed

+329
-101
lines changed

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use super::poisson_disk::poisson_disk_sample;
2+
use crate::vector::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
23
use crate::vector::misc::{PointSpacingType, dvec2_to_point};
34
use glam::DVec2;
4-
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, Rect, Shape};
5+
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape};
56

67
/// Splits the [`BezPath`] at `t` value which lie in the range of [0, 1].
78
/// Returns [`None`] if the given [`BezPath`] has no segments or `t` is within f64::EPSILON of 0 or 1.
@@ -314,3 +315,16 @@ pub fn poisson_disk_points(bezpath_index: usize, bezpaths: &[(BezPath, Rect)], s
314315

315316
poisson_disk_sample(offset, width, height, separation_disk_diameter, point_in_shape_checker, line_intersect_shape_checker, rng)
316317
}
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: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use bezier_rs::BezierHandles;
12
use dyn_any::DynAny;
23
use glam::DVec2;
3-
use kurbo::Point;
4+
use kurbo::{CubicBez, Line, PathSeg, Point, QuadBez};
45

56
/// Represents different ways of calculating the centroid.
67
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
@@ -96,3 +97,37 @@ pub fn point_to_dvec2(point: Point) -> DVec2 {
9697
pub fn dvec2_to_point(value: DVec2) -> Point {
9798
Point { x: value.x, y: value.y }
9899
}
100+
101+
pub fn segment_to_handles(segment: &PathSeg) -> BezierHandles {
102+
match *segment {
103+
PathSeg::Line(_) => BezierHandles::Linear,
104+
PathSeg::Quad(QuadBez { p0: _, p1, p2: _ }) => BezierHandles::Quadratic { handle: point_to_dvec2(p1) },
105+
PathSeg::Cubic(CubicBez { p0: _, p1, p2, p3: _ }) => BezierHandles::Cubic {
106+
handle_start: point_to_dvec2(p1),
107+
handle_end: point_to_dvec2(p2),
108+
},
109+
}
110+
}
111+
112+
pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> PathSeg {
113+
match handles {
114+
bezier_rs::BezierHandles::Linear => {
115+
let p0 = dvec2_to_point(start);
116+
let p1 = dvec2_to_point(end);
117+
PathSeg::Line(Line::new(p0, p1))
118+
}
119+
bezier_rs::BezierHandles::Quadratic { handle } => {
120+
let p0 = dvec2_to_point(start);
121+
let p1 = dvec2_to_point(handle);
122+
let p2 = dvec2_to_point(end);
123+
PathSeg::Quad(QuadBez::new(p0, p1, p2))
124+
}
125+
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
126+
let p0 = dvec2_to_point(start);
127+
let p1 = dvec2_to_point(handle_start);
128+
let p2 = dvec2_to_point(handle_end);
129+
let p3 = dvec2_to_point(end);
130+
PathSeg::Cubic(CubicBez::new(p0, p1, p2, p3))
131+
}
132+
}
133+
}

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

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,10 @@ impl VectorData {
226226

227227
pub fn close_subpaths(&mut self) {
228228
let segments_to_add: Vec<_> = self
229-
.stroke_bezier_paths()
230-
.filter(|subpath| !subpath.closed)
231-
.filter_map(|subpath| {
232-
let (first, last) = subpath.manipulator_groups().first().zip(subpath.manipulator_groups().last())?;
229+
.build_stroke_path_iter()
230+
.filter(|(_, closed)| !closed)
231+
.filter_map(|(manipulator_groups, _)| {
232+
let (first, last) = manipulator_groups.first().zip(manipulator_groups.last())?;
233233
let (start, end) = self.point_domain.resolve_id(first.id).zip(self.point_domain.resolve_id(last.id))?;
234234
Some((start, end))
235235
})
@@ -370,7 +370,7 @@ impl VectorData {
370370
}
371371

372372
pub fn check_point_inside_shape(&self, vector_data_transform: DAffine2, point: DVec2) -> bool {
373-
let bez_paths: Vec<_> = self
373+
let number = self
374374
.stroke_bezpath_iter()
375375
.map(|mut bezpath| {
376376
// TODO: apply transform to points instead of modifying the paths
@@ -379,19 +379,9 @@ impl VectorData {
379379
let bbox = bezpath.bounding_box();
380380
(bezpath, bbox)
381381
})
382-
.collect();
383-
384-
// Check against all paths the point is contained in to compute the correct winding number
385-
let mut number = 0;
386-
387-
for (shape, bbox) in bez_paths {
388-
if bbox.x0 > point.x || bbox.y0 > point.y || bbox.x1 < point.x || bbox.y1 < point.y {
389-
continue;
390-
}
391-
392-
let winding = shape.winding(dvec2_to_point(point));
393-
number += winding;
394-
}
382+
.filter(|(_, bbox)| bbox.contains(dvec2_to_point(point)))
383+
.map(|(bezpath, _)| bezpath.winding(dvec2_to_point(point)))
384+
.sum::<i32>();
395385

396386
// Non-zero fill rule
397387
number != 0

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,35 @@ impl SegmentDomain {
440440
let handles = self.handles.iter_mut();
441441
zip(ids, zip(start_point, zip(end_point, handles))).map(|(id, (start_point, (end_point, handles)))| (id, start_point, end_point, handles))
442442
}
443+
444+
pub(crate) fn pair_handles_and_points_mut_by_index(
445+
&mut self,
446+
index1: usize,
447+
index2: usize,
448+
) -> (&mut bezier_rs::BezierHandles, &mut usize, &mut usize, &mut bezier_rs::BezierHandles, &mut usize, &mut usize) {
449+
// Use split_at_mut to avoid multiple mutable borrows of the same slice
450+
let (handles_first, handles_second) = self.handles.split_at_mut(index2.max(index1));
451+
let (start_first, start_second) = self.start_point.split_at_mut(index2.max(index1));
452+
let (end_first, end_second) = self.end_point.split_at_mut(index2.max(index1));
453+
454+
let (h1, h2) = if index1 < index2 {
455+
(&mut handles_first[index1], &mut handles_second[0])
456+
} else {
457+
(&mut handles_second[0], &mut handles_first[index2])
458+
};
459+
let (sp1, sp2) = if index1 < index2 {
460+
(&mut start_first[index1], &mut start_second[0])
461+
} else {
462+
(&mut start_second[0], &mut start_first[index2])
463+
};
464+
let (ep1, ep2) = if index1 < index2 {
465+
(&mut end_first[index1], &mut end_second[0])
466+
} else {
467+
(&mut end_second[0], &mut end_first[index2])
468+
};
469+
470+
(h1, sp1, ep1, h2, sp2, ep2)
471+
}
443472
}
444473

445474
#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]

0 commit comments

Comments
 (0)