From 59d14396f7cc24f49da5edbb2959560cd2756dfa Mon Sep 17 00:00:00 2001 From: indierusty Date: Sun, 24 Aug 2025 21:16:13 +0530 Subject: [PATCH 1/4] fix insideness checking --- .../vector/algorithms/bezpath_algorithms.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs index 6a1213d55e..4a2c6a4e41 100644 --- a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs +++ b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs @@ -617,28 +617,19 @@ pub fn bezpath_is_inside_bezpath(bezpath1: &BezPath, bezpath2: &BezPath, accurac let inner_bbox = bezpath1.bounding_box(); let outer_bbox = bezpath2.bounding_box(); - // Eliminate bezpath1 if its bounding box is not completely inside the bezpath2's bounding box. - // Reasoning: - // If the inner bezpath bounding box is larger than the outer bezpath bounding box in any direction - // then the inner bezpath is intersecting with or outside the outer bezpath. - if !outer_bbox.contains_rect(inner_bbox) { - return false; - } - - // Eliminate bezpath1 if any of its anchor points are outside the bezpath2. - if !bezpath1.elements().iter().filter_map(|el| el.end_point()).all(|point| bezpath2.contains(point)) { + // Eliminate 'bezpath1' if its bounding box is completely outside the bezpath2's bounding box + if !outer_bbox.contains_rect(inner_bbox) && outer_bbox.intersect(inner_bbox).is_zero_area() { return false; } // Eliminate this subpath if it intersects with the other subpath. - if !bezpath_intersections(bezpath1, bezpath2, accuracy, minimum_separation).is_empty() { + if !bezpath_intersections(bezpath1, &bezpath2, accuracy, minimum_separation).is_empty() { return false; } // At this point: - // (1) This subpath's bounding box is inside the other subpath's bounding box, - // (2) Its anchors are inside the other subpath, and - // (3) It is not intersecting with the other subpath. + // (1) The 'bezpath1' bounding box either intersect or is inside the 'bezpath2's bounding box, + // (2) The 'bezpath1' is not intersecting with the 'bezpath2'. // Hence, this subpath is completely inside the given other subpath. true } From 7b5505faadc66a8de1dffa988c01ba80d7a6344e Mon Sep 17 00:00:00 2001 From: indierusty Date: Tue, 26 Aug 2025 08:49:52 +0530 Subject: [PATCH 2/4] fix selection insideness checking --- .../src/vector/algorithms/bezpath_algorithms.rs | 13 ++++++++++--- node-graph/gcore/src/vector/click_target.rs | 10 ++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs index 4a2c6a4e41..d40fa7002c 100644 --- a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs +++ b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs @@ -608,6 +608,7 @@ pub fn round_line_join(bezpath1: &BezPath, bezpath2: &BezPath, center: DVec2) -> } /// Returns `true` if the `bezpath1` is completely inside the `bezpath2`. +/// NOTE: The `bezpath2` has to be a closed path to get correct result. pub fn bezpath_is_inside_bezpath(bezpath1: &BezPath, bezpath2: &BezPath, accuracy: Option, minimum_separation: Option) -> bool { // Eliminate any possibility of one being inside the other, if either of them is empty if bezpath1.is_empty() || bezpath2.is_empty() { @@ -617,19 +618,25 @@ pub fn bezpath_is_inside_bezpath(bezpath1: &BezPath, bezpath2: &BezPath, accurac let inner_bbox = bezpath1.bounding_box(); let outer_bbox = bezpath2.bounding_box(); - // Eliminate 'bezpath1' if its bounding box is completely outside the bezpath2's bounding box + // Eliminate if the 'bezpath1' bounding box is completely outside the bezpath2's bounding box if !outer_bbox.contains_rect(inner_bbox) && outer_bbox.intersect(inner_bbox).is_zero_area() { return false; } - // Eliminate this subpath if it intersects with the other subpath. + // Eliminate if any anchors point of the 'bezpath1' is outside the 'bezpath2'. + if !bezpath1.elements().iter().filter_map(|elm| elm.end_point()).all(|point| bezpath2.contains(point)) { + return false; + } + + // Eliminate if 'bezpath1' intersects with 'bezpath2'. if !bezpath_intersections(bezpath1, &bezpath2, accuracy, minimum_separation).is_empty() { return false; } // At this point: // (1) The 'bezpath1' bounding box either intersect or is inside the 'bezpath2's bounding box, - // (2) The 'bezpath1' is not intersecting with the 'bezpath2'. + // (2) All the anchor point of the 'bezpath1' is inside 'bezpath2' + // (3) The 'bezpath1' is not intersecting with the 'bezpath2'. // Hence, this subpath is completely inside the given other subpath. true } diff --git a/node-graph/gcore/src/vector/click_target.rs b/node-graph/gcore/src/vector/click_target.rs index 976f8682ae..6ab7c1860a 100644 --- a/node-graph/gcore/src/vector/click_target.rs +++ b/node-graph/gcore/src/vector/click_target.rs @@ -1,4 +1,4 @@ -use super::algorithms::intersection::filtered_segment_intersections; +use super::algorithms::{bezpath_algorithms::bezpath_is_inside_bezpath, intersection::filtered_segment_intersections}; use super::misc::dvec2_to_point; use crate::math::math_ext::QuadExt; use crate::math::quad::Quad; @@ -6,7 +6,7 @@ use crate::subpath::Subpath; use crate::vector::PointId; use crate::vector::misc::point_to_dvec2; use glam::{DAffine2, DMat2, DVec2}; -use kurbo::{Affine, ParamCurve, PathSeg, Point, Shape}; +use kurbo::{Affine, BezPath, ParamCurve, PathSeg, Shape}; #[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct FreePoint { @@ -125,9 +125,11 @@ impl ClickTarget { return true; } + let mut selection = BezPath::from_path_segments(bezier_iter()); + selection.close_path(); + // 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(Point::new(shape_point.x, shape_point.y))).sum::() != 0) + bezpath_is_inside_bezpath(&subpath.to_bezpath(), &selection, None, None) } ClickTargetType::FreePoint(point) => bezier_iter().map(|bezier: PathSeg| bezier.winding(dvec2_to_point(point.position))).sum::() != 0, } From 5455e55111f40c66c144c0844186a7b5d568067f Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 25 Aug 2025 21:32:10 -0700 Subject: [PATCH 3/4] Revert changes to comments --- .../vector/algorithms/bezpath_algorithms.rs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs index d40fa7002c..ea3db7f9d3 100644 --- a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs +++ b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs @@ -608,9 +608,9 @@ pub fn round_line_join(bezpath1: &BezPath, bezpath2: &BezPath, center: DVec2) -> } /// Returns `true` if the `bezpath1` is completely inside the `bezpath2`. -/// NOTE: The `bezpath2` has to be a closed path to get correct result. +/// NOTE: `bezpath2` must be a closed path to get correct results. pub fn bezpath_is_inside_bezpath(bezpath1: &BezPath, bezpath2: &BezPath, accuracy: Option, minimum_separation: Option) -> bool { - // Eliminate any possibility of one being inside the other, if either of them is empty + // Eliminate any possibility of one being inside the other, if either of them are empty if bezpath1.is_empty() || bezpath2.is_empty() { return false; } @@ -618,25 +618,28 @@ pub fn bezpath_is_inside_bezpath(bezpath1: &BezPath, bezpath2: &BezPath, accurac let inner_bbox = bezpath1.bounding_box(); let outer_bbox = bezpath2.bounding_box(); - // Eliminate if the 'bezpath1' bounding box is completely outside the bezpath2's bounding box + // Eliminate bezpath1 if its bounding box is not completely inside the bezpath2's bounding box. + // Reasoning: + // If the inner bezpath bounding box is larger than the outer bezpath bounding box in any direction + // then the inner bezpath is intersecting with or outside the outer bezpath. if !outer_bbox.contains_rect(inner_bbox) && outer_bbox.intersect(inner_bbox).is_zero_area() { return false; } - // Eliminate if any anchors point of the 'bezpath1' is outside the 'bezpath2'. + // Eliminate bezpath1 if any of its anchor points are outside the bezpath2. if !bezpath1.elements().iter().filter_map(|elm| elm.end_point()).all(|point| bezpath2.contains(point)) { return false; } - // Eliminate if 'bezpath1' intersects with 'bezpath2'. - if !bezpath_intersections(bezpath1, &bezpath2, accuracy, minimum_separation).is_empty() { + // Eliminate this subpath if it intersects with the other subpath. + if !bezpath_intersections(bezpath1, bezpath2, accuracy, minimum_separation).is_empty() { return false; } // At this point: - // (1) The 'bezpath1' bounding box either intersect or is inside the 'bezpath2's bounding box, - // (2) All the anchor point of the 'bezpath1' is inside 'bezpath2' - // (3) The 'bezpath1' is not intersecting with the 'bezpath2'. + // (1) This subpath's bounding box is inside the other subpath's bounding box, + // (2) Its anchors are inside the other subpath, and + // (3) It is not intersecting with the other subpath. // Hence, this subpath is completely inside the given other subpath. true } From cbca6790d59f4f36d9d8ebaf81ee84b930366a2d Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 27 Aug 2025 13:50:14 -0700 Subject: [PATCH 4/4] Revert unneed rename --- node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs index ea3db7f9d3..a18dda6314 100644 --- a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs +++ b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs @@ -627,7 +627,7 @@ pub fn bezpath_is_inside_bezpath(bezpath1: &BezPath, bezpath2: &BezPath, accurac } // Eliminate bezpath1 if any of its anchor points are outside the bezpath2. - if !bezpath1.elements().iter().filter_map(|elm| elm.end_point()).all(|point| bezpath2.contains(point)) { + if !bezpath1.elements().iter().filter_map(|el| el.end_point()).all(|point| bezpath2.contains(point)) { return false; }