From 3dc43edd2599ca4827387ff0f63f78b7192fd103 Mon Sep 17 00:00:00 2001 From: indierusty Date: Fri, 3 Jan 2025 16:41:22 +0530 Subject: [PATCH 1/6] add function to calculate if a subpath is inside polygon --- libraries/bezier-rs/src/utils.rs | 38 ++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/libraries/bezier-rs/src/utils.rs b/libraries/bezier-rs/src/utils.rs index 5e36dc2ca1..f5334b0eb3 100644 --- a/libraries/bezier-rs/src/utils.rs +++ b/libraries/bezier-rs/src/utils.rs @@ -1,5 +1,5 @@ use crate::consts::{MAX_ABSOLUTE_DIFFERENCE, STRICT_MAX_ABSOLUTE_DIFFERENCE}; -use crate::ManipulatorGroup; +use crate::{ManipulatorGroup, Subpath}; use glam::{BVec2, DMat2, DVec2}; @@ -179,6 +179,19 @@ pub fn do_rectangles_overlap(rectangle1: [DVec2; 2], rectangle2: [DVec2; 2]) -> top_right1.x >= bottom_left2.x && top_right2.x >= bottom_left1.x && top_right2.y >= bottom_left1.y && top_right1.y >= bottom_left2.y } +pub fn is_subpath_inside_polygon(polygon: &[DVec2], subpath: &Subpath) -> bool { + let polypath = Subpath::from_anchors_linear(polygon.to_vec(), true); + if !subpath.subpath_intersections(&polypath, Some(0.02), Some(0.05)).is_empty() { + return false; + } + for anchors in subpath.anchors() { + if !polypath.contains_point(anchors) { + return false; + } + } + true +} + /// Returns the intersection of two lines. The lines are given by a point on the line and its slope (represented by a vector). pub fn line_intersection(point1: DVec2, point1_slope_vector: DVec2, point2: DVec2, point2_slope_vector: DVec2) -> DVec2 { assert!(point1_slope_vector.normalize() != point2_slope_vector.normalize()); @@ -286,7 +299,7 @@ pub fn compute_circular_subpath_details(left: DVec2, #[cfg(test)] mod tests { use super::*; - use crate::consts::MAX_ABSOLUTE_DIFFERENCE; + use crate::{consts::MAX_ABSOLUTE_DIFFERENCE, Bezier, EmptyId}; /// Compare vectors of `f64`s with a provided max absolute value difference. fn f64_compare_vector(a: Vec, b: Vec, max_abs_diff: f64) -> bool { @@ -352,6 +365,27 @@ mod tests { assert!(!do_rectangles_overlap([DVec2::new(0., 0.), DVec2::new(10., 10.)], [DVec2::new(0., 20.), DVec2::new(20., 30.)])); } + #[test] + fn test_is_subpath_inside_polygon() { + let lasso_polygon = vec![DVec2::new(100., 100.), DVec2::new(500., 100.), DVec2::new(500., 500.), DVec2::new(100., 500.)]; + + let curve = Bezier::from_quadratic_dvec2(DVec2::new(189., 289.), DVec2::new(9., 286.), DVec2::new(45., 410.)); + let curve_intersecting = Subpath::::from_bezier(&curve); + assert_eq!(is_subpath_inside_polygon(&lasso_polygon, &curve_intersecting), false); + + let curve = Bezier::from_quadratic_dvec2(DVec2::new(115., 37.), DVec2::new(51.4, 91.8), DVec2::new(76.5, 242.)); + let curve_outside = Subpath::::from_bezier(&curve); + assert_eq!(is_subpath_inside_polygon(&lasso_polygon, &curve_outside), false); + + let curve = Bezier::from_cubic_dvec2(DVec2::new(210.1, 133.5), DVec2::new(150.2, 436.9), DVec2::new(436., 285.), DVec2::new(247.6, 240.7)); + let curve_inside = Subpath::::from_bezier(&curve); + assert_eq!(is_subpath_inside_polygon(&lasso_polygon, &curve_inside), true); + + let line = Bezier::from_linear_dvec2(DVec2::new(101., 101.5), DVec2::new(150.2, 499.)); + let line_inside = Subpath::::from_bezier(&line); + assert_eq!(is_subpath_inside_polygon(&lasso_polygon, &line_inside), true); + } + #[test] fn test_find_intersection() { // y = 2x + 10 From 36a2d86a0557ac45ba30613597d04fbf1ab054fe Mon Sep 17 00:00:00 2001 From: indierusty Date: Sat, 4 Jan 2025 10:14:04 +0530 Subject: [PATCH 2/6] make is_subpath_inside_polygon() flexible --- libraries/bezier-rs/src/utils.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/bezier-rs/src/utils.rs b/libraries/bezier-rs/src/utils.rs index f5334b0eb3..f39d02b271 100644 --- a/libraries/bezier-rs/src/utils.rs +++ b/libraries/bezier-rs/src/utils.rs @@ -179,13 +179,12 @@ pub fn do_rectangles_overlap(rectangle1: [DVec2; 2], rectangle2: [DVec2; 2]) -> top_right1.x >= bottom_left2.x && top_right2.x >= bottom_left1.x && top_right2.y >= bottom_left1.y && top_right1.y >= bottom_left2.y } -pub fn is_subpath_inside_polygon(polygon: &[DVec2], subpath: &Subpath) -> bool { - let polypath = Subpath::from_anchors_linear(polygon.to_vec(), true); - if !subpath.subpath_intersections(&polypath, Some(0.02), Some(0.05)).is_empty() { +pub fn is_subpath_inside_polygon(polygon: &Subpath, subpath: &Subpath) -> bool { + if !subpath.subpath_intersections(&polygon, None, None).is_empty() { return false; } for anchors in subpath.anchors() { - if !polypath.contains_point(anchors) { + if !polygon.contains_point(anchors) { return false; } } @@ -367,7 +366,8 @@ mod tests { #[test] fn test_is_subpath_inside_polygon() { - let lasso_polygon = vec![DVec2::new(100., 100.), DVec2::new(500., 100.), DVec2::new(500., 500.), DVec2::new(100., 500.)]; + let lasso_polygon = [DVec2::new(100., 100.), DVec2::new(500., 100.), DVec2::new(500., 500.), DVec2::new(100., 500.)].to_vec(); + let lasso_polygon = Subpath::from_anchors_linear(lasso_polygon, true); let curve = Bezier::from_quadratic_dvec2(DVec2::new(189., 289.), DVec2::new(9., 286.), DVec2::new(45., 410.)); let curve_intersecting = Subpath::::from_bezier(&curve); From 25d2d75b5d60a1b63ae2d99c7dbec42a4d23ce16 Mon Sep 17 00:00:00 2001 From: indierusty Date: Sat, 4 Jan 2025 18:17:30 +0530 Subject: [PATCH 3/6] obtimize is_subpath_inside_polygon function --- libraries/bezier-rs/src/utils.rs | 46 +++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/libraries/bezier-rs/src/utils.rs b/libraries/bezier-rs/src/utils.rs index f39d02b271..23b21b0465 100644 --- a/libraries/bezier-rs/src/utils.rs +++ b/libraries/bezier-rs/src/utils.rs @@ -171,7 +171,7 @@ pub fn solve_cubic(a: f64, b: f64, c: f64, d: f64) -> [Option; 3] { } } -/// Determine if two rectangles have any overlap. The rectangles are represented by a pair of coordinates that designate the top left and bottom right corners (in a graphical coordinate system). +/// Check if two rectangles have any overlap. The rectangles are represented by a pair of coordinates that designate the top left and bottom right corners (in a graphical coordinate system). pub fn do_rectangles_overlap(rectangle1: [DVec2; 2], rectangle2: [DVec2; 2]) -> bool { let [bottom_left1, top_right1] = rectangle1; let [bottom_left2, top_right2] = rectangle2; @@ -179,15 +179,43 @@ pub fn do_rectangles_overlap(rectangle1: [DVec2; 2], rectangle2: [DVec2; 2]) -> top_right1.x >= bottom_left2.x && top_right2.x >= bottom_left1.x && top_right2.y >= bottom_left1.y && top_right1.y >= bottom_left2.y } +/// Check if a point is completely inside rectangle which is respresented as pair of coordinates [top-left, bottom-right]. +pub fn is_point_inside_rectangle(rect: [DVec2; 2], point: DVec2) -> bool { + let [top_left, bottom_rigth] = rect; + point.x > top_left.x && point.x < bottom_rigth.x && point.y > top_left.y && point.y < bottom_rigth.y +} + +/// Check if inner rectangle is completely inside outer rectangle. The rectangles are represented as pair of coordinates [top-left, bottom-right]. +pub fn is_rectangle_inside_other(inner: [DVec2; 2], outer: [DVec2; 2]) -> bool { + is_point_inside_rectangle(outer, inner[0]) && is_point_inside_rectangle(outer, inner[1]) +} + +/// Check if subpath is completely inside polygon (closed subpath). pub fn is_subpath_inside_polygon(polygon: &Subpath, subpath: &Subpath) -> bool { - if !subpath.subpath_intersections(&polygon, None, None).is_empty() { - return false; - } + // Eliminate subpath if its bounding box is not completely inside polygon bounding box + if !subpath.is_empty() && !polygon.is_empty() { + let subpath_bbox = subpath.bounding_box().unwrap(); + let polygon_bbox = polygon.bounding_box().unwrap(); + + // subpath is can only be completely inside polygon path if subpath bounding box is completely inside polygon bounding box + if !is_rectangle_inside_other(subpath_bbox, polygon_bbox) { + return false; + } + }; + + // Eliminate subpath if its any of the anchors is outside polygon for anchors in subpath.anchors() { if !polygon.contains_point(anchors) { return false; } } + + // Eliminate subpath which is intersecting with the polygon + if !subpath.subpath_intersections(&polygon, None, None).is_empty() { + return false; + } + + // here (1) subpath bbox is inside polygon bbox, (2) its anchors are inside polygon and (3) it is not intersecting with polygon true } @@ -364,6 +392,16 @@ mod tests { assert!(!do_rectangles_overlap([DVec2::new(0., 0.), DVec2::new(10., 10.)], [DVec2::new(0., 20.), DVec2::new(20., 30.)])); } + #[test] + fn test_is_rectangle_inside_other() { + assert!(!is_rectangle_inside_other([DVec2::new(10., 10.), DVec2::new(50., 50.)], [DVec2::new(10., 10.), DVec2::new(50., 50.)])); + assert!(is_rectangle_inside_other( + [DVec2::new(10.01, 10.01), DVec2::new(49., 49.)], + [DVec2::new(10., 10.), DVec2::new(50., 50.)] + )); + assert!(!is_rectangle_inside_other([DVec2::new(5., 5.), DVec2::new(50., 9.99)], [DVec2::new(10., 10.), DVec2::new(50., 50.)])); + } + #[test] fn test_is_subpath_inside_polygon() { let lasso_polygon = [DVec2::new(100., 100.), DVec2::new(500., 100.), DVec2::new(500., 500.), DVec2::new(100., 500.)].to_vec(); From ac7ac658f23730fb83b315e7d2beb9c5bbf38f56 Mon Sep 17 00:00:00 2001 From: indierusty Date: Tue, 7 Jan 2025 10:07:28 +0530 Subject: [PATCH 4/6] move is_inside_subpath function to Subpath struct method --- libraries/bezier-rs/src/subpath/solvers.rs | 55 +++++++++++++++++++++- libraries/bezier-rs/src/utils.rs | 51 -------------------- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/libraries/bezier-rs/src/subpath/solvers.rs b/libraries/bezier-rs/src/subpath/solvers.rs index 0c9ab58bb9..d573afbd3e 100644 --- a/libraries/bezier-rs/src/subpath/solvers.rs +++ b/libraries/bezier-rs/src/subpath/solvers.rs @@ -1,6 +1,6 @@ use super::*; use crate::consts::MAX_ABSOLUTE_DIFFERENCE; -use crate::utils::{compute_circular_subpath_details, line_intersection, SubpathTValue}; +use crate::utils::{compute_circular_subpath_details, is_rectangle_inside_other, line_intersection, SubpathTValue}; use crate::TValue; use glam::{DAffine2, DMat2, DVec2}; @@ -237,6 +237,37 @@ impl Subpath { false } + /// Returns `true` if this subpath is completely inside the other subpath. + pub fn is_inside_subpath(&self, other: &Subpath, error: Option, minimum_separation: Option) -> bool { + // Eliminate this subpath if its bounding box is not completely inside other subpath bounding box + if !self.is_empty() && !other.is_empty() { + let inner_bbox = self.bounding_box().unwrap(); + let outer_bbox = other.bounding_box().unwrap(); + // Reasoning: + // (min x, min y) of inner subpath is less or equal to the outer (min x, min y) or + // (min x, min y) of inner subpath is more or equal to outer (max x, mix y) then the inner is intersecting or is outside the outer. (same will be true for (max x, max y)) + if !is_rectangle_inside_other(inner_bbox, outer_bbox) { + return false; + } + }; + + // Eliminate this if any of its the subpath's anchors is outside other's subpath + for anchors in self.anchors() { + if !other.contains_point(anchors) { + return false; + } + } + + // Eliminate this if its subpath is intersecting wih the other's subpath + if !self.subpath_intersections(&other, error, minimum_separation).is_empty() { + return false; + } + + // here (1) this subpath bbox is inside other bbox, (2) its anchors are inside other subpath and (3) it is not intersecting with other subpath. + // hence this this subpath is completely inside given other subpath. + true + } + /// Returns a normalized unit vector representing the tangent on the subpath based on the parametric `t`-value provided. /// pub fn tangent(&self, t: SubpathTValue) -> DVec2 { @@ -876,6 +907,28 @@ mod tests { // TODO: add more intersection tests + #[test] + fn is_inside_subpath() { + let lasso_polygon = [DVec2::new(100., 100.), DVec2::new(500., 100.), DVec2::new(500., 500.), DVec2::new(100., 500.)].to_vec(); + let lasso_polygon = Subpath::from_anchors_linear(lasso_polygon, true); + + let curve = Bezier::from_quadratic_dvec2(DVec2::new(189., 289.), DVec2::new(9., 286.), DVec2::new(45., 410.)); + let curve_intersecting = Subpath::::from_bezier(&curve); + assert_eq!(curve_intersecting.is_inside_subpath(&lasso_polygon, None, None), false); + + let curve = Bezier::from_quadratic_dvec2(DVec2::new(115., 37.), DVec2::new(51.4, 91.8), DVec2::new(76.5, 242.)); + let curve_outside = Subpath::::from_bezier(&curve); + assert_eq!(curve_outside.is_inside_subpath(&lasso_polygon, None, None), false); + + let curve = Bezier::from_cubic_dvec2(DVec2::new(210.1, 133.5), DVec2::new(150.2, 436.9), DVec2::new(436., 285.), DVec2::new(247.6, 240.7)); + let curve_inside = Subpath::::from_bezier(&curve); + assert_eq!(curve_inside.is_inside_subpath(&lasso_polygon, None, None), true); + + let line = Bezier::from_linear_dvec2(DVec2::new(101., 101.5), DVec2::new(150.2, 499.)); + let line_inside = Subpath::::from_bezier(&line); + assert_eq!(line_inside.is_inside_subpath(&lasso_polygon, None, None), true); + } + #[test] fn round_join_counter_clockwise_rotation() { // Test case where the round join is drawn in the counter clockwise direction between two consecutive offsets diff --git a/libraries/bezier-rs/src/utils.rs b/libraries/bezier-rs/src/utils.rs index 23b21b0465..71612be50c 100644 --- a/libraries/bezier-rs/src/utils.rs +++ b/libraries/bezier-rs/src/utils.rs @@ -190,35 +190,6 @@ pub fn is_rectangle_inside_other(inner: [DVec2; 2], outer: [DVec2; 2]) -> bool { is_point_inside_rectangle(outer, inner[0]) && is_point_inside_rectangle(outer, inner[1]) } -/// Check if subpath is completely inside polygon (closed subpath). -pub fn is_subpath_inside_polygon(polygon: &Subpath, subpath: &Subpath) -> bool { - // Eliminate subpath if its bounding box is not completely inside polygon bounding box - if !subpath.is_empty() && !polygon.is_empty() { - let subpath_bbox = subpath.bounding_box().unwrap(); - let polygon_bbox = polygon.bounding_box().unwrap(); - - // subpath is can only be completely inside polygon path if subpath bounding box is completely inside polygon bounding box - if !is_rectangle_inside_other(subpath_bbox, polygon_bbox) { - return false; - } - }; - - // Eliminate subpath if its any of the anchors is outside polygon - for anchors in subpath.anchors() { - if !polygon.contains_point(anchors) { - return false; - } - } - - // Eliminate subpath which is intersecting with the polygon - if !subpath.subpath_intersections(&polygon, None, None).is_empty() { - return false; - } - - // here (1) subpath bbox is inside polygon bbox, (2) its anchors are inside polygon and (3) it is not intersecting with polygon - true -} - /// Returns the intersection of two lines. The lines are given by a point on the line and its slope (represented by a vector). pub fn line_intersection(point1: DVec2, point1_slope_vector: DVec2, point2: DVec2, point2_slope_vector: DVec2) -> DVec2 { assert!(point1_slope_vector.normalize() != point2_slope_vector.normalize()); @@ -402,28 +373,6 @@ mod tests { assert!(!is_rectangle_inside_other([DVec2::new(5., 5.), DVec2::new(50., 9.99)], [DVec2::new(10., 10.), DVec2::new(50., 50.)])); } - #[test] - fn test_is_subpath_inside_polygon() { - let lasso_polygon = [DVec2::new(100., 100.), DVec2::new(500., 100.), DVec2::new(500., 500.), DVec2::new(100., 500.)].to_vec(); - let lasso_polygon = Subpath::from_anchors_linear(lasso_polygon, true); - - let curve = Bezier::from_quadratic_dvec2(DVec2::new(189., 289.), DVec2::new(9., 286.), DVec2::new(45., 410.)); - let curve_intersecting = Subpath::::from_bezier(&curve); - assert_eq!(is_subpath_inside_polygon(&lasso_polygon, &curve_intersecting), false); - - let curve = Bezier::from_quadratic_dvec2(DVec2::new(115., 37.), DVec2::new(51.4, 91.8), DVec2::new(76.5, 242.)); - let curve_outside = Subpath::::from_bezier(&curve); - assert_eq!(is_subpath_inside_polygon(&lasso_polygon, &curve_outside), false); - - let curve = Bezier::from_cubic_dvec2(DVec2::new(210.1, 133.5), DVec2::new(150.2, 436.9), DVec2::new(436., 285.), DVec2::new(247.6, 240.7)); - let curve_inside = Subpath::::from_bezier(&curve); - assert_eq!(is_subpath_inside_polygon(&lasso_polygon, &curve_inside), true); - - let line = Bezier::from_linear_dvec2(DVec2::new(101., 101.5), DVec2::new(150.2, 499.)); - let line_inside = Subpath::::from_bezier(&line); - assert_eq!(is_subpath_inside_polygon(&lasso_polygon, &line_inside), true); - } - #[test] fn test_find_intersection() { // y = 2x + 10 From e7ca0a7599d3ab310e82f5bb7b022f75b5acc1d8 Mon Sep 17 00:00:00 2001 From: indierusty Date: Tue, 7 Jan 2025 10:11:55 +0530 Subject: [PATCH 5/6] add interactive demo for subpath insideness --- .../bezier-rs-demos/src/features-subpath.ts | 21 +++++++++++++++++++ .../other/bezier-rs-demos/wasm/src/subpath.rs | 15 +++++++++++++ 2 files changed, 36 insertions(+) diff --git a/website/other/bezier-rs-demos/src/features-subpath.ts b/website/other/bezier-rs-demos/src/features-subpath.ts index 7c5371f727..9568275ed0 100644 --- a/website/other/bezier-rs-demos/src/features-subpath.ts +++ b/website/other/bezier-rs-demos/src/features-subpath.ts @@ -142,6 +142,27 @@ const subpathFeatures = { ), inputOptions: [intersectionErrorOptions, minimumSeparationOptions], }, + "inside-other": { + name: "Inside (Other Subpath)", + callback: (subpath: WasmSubpathInstance, options: Record): string => + subpath.inside_subpath( + [ + [40, 40], + [160, 40], + [160, 80], + [200, 100], + [160, 120], + [160, 160], + [40, 160], + [40, 120], + [80, 100], + [40, 80], + ], + options.error, + options.minimum_separation, + ), + inputOptions: [intersectionErrorOptions, minimumSeparationOptions], + }, curvature: { name: "Curvature", callback: (subpath: WasmSubpathInstance, options: Record, _: undefined): string => subpath.curvature(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]), diff --git a/website/other/bezier-rs-demos/wasm/src/subpath.rs b/website/other/bezier-rs-demos/wasm/src/subpath.rs index 4cb738d449..026c7b3fa9 100644 --- a/website/other/bezier-rs-demos/wasm/src/subpath.rs +++ b/website/other/bezier-rs-demos/wasm/src/subpath.rs @@ -443,6 +443,21 @@ impl WasmSubpath { wrap_svg_tag(format!("{subpath_svg}{rectangle_svg}{intersections_svg}")) } + pub fn inside_subpath(&self, js_points: JsValue, error: f64, minimum_separation: f64) -> String { + let array = js_points.dyn_into::().unwrap(); + let points = array.iter().map(|p| parse_point(&p)); + let other = Subpath::::from_anchors(points, true); + + let is_inside = self.0.is_inside_subpath(&other, Some(error), Some(minimum_separation)); + let color = if is_inside { RED } else { BLACK }; + + let self_svg = self.to_default_svg(); + let mut other_svg = String::new(); + other.curve_to_svg(&mut other_svg, CURVE_ATTRIBUTES.replace(BLACK, color)); + + wrap_svg_tag(format!("{self_svg}{other_svg}")) + } + pub fn curvature(&self, t: f64, t_variant: String) -> String { let subpath = self.to_default_svg(); let t = parse_t_variant(&t_variant, t); From 84a41dcf46e58c8b5bd0c92abde29ebbdd919ba8 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 9 Jan 2025 23:07:15 -0800 Subject: [PATCH 6/6] Code review --- libraries/bezier-rs/src/subpath/solvers.rs | 57 +++++++++++++--------- libraries/bezier-rs/src/utils.rs | 10 ++-- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/libraries/bezier-rs/src/subpath/solvers.rs b/libraries/bezier-rs/src/subpath/solvers.rs index d573afbd3e..f5463f6f44 100644 --- a/libraries/bezier-rs/src/subpath/solvers.rs +++ b/libraries/bezier-rs/src/subpath/solvers.rs @@ -237,34 +237,43 @@ impl Subpath { false } - /// Returns `true` if this subpath is completely inside the other subpath. + /// Returns `true` if this subpath is completely inside the `other` subpath. pub fn is_inside_subpath(&self, other: &Subpath, error: Option, minimum_separation: Option) -> bool { - // Eliminate this subpath if its bounding box is not completely inside other subpath bounding box - if !self.is_empty() && !other.is_empty() { - let inner_bbox = self.bounding_box().unwrap(); - let outer_bbox = other.bounding_box().unwrap(); - // Reasoning: - // (min x, min y) of inner subpath is less or equal to the outer (min x, min y) or - // (min x, min y) of inner subpath is more or equal to outer (max x, mix y) then the inner is intersecting or is outside the outer. (same will be true for (max x, max y)) - if !is_rectangle_inside_other(inner_bbox, outer_bbox) { - return false; - } - }; + // Eliminate any possibility of one being inside the other, if either of them is empty + if self.is_empty() || other.is_empty() { + return false; + } + + // Safe to unwrap because the subpath is not empty + let inner_bbox = self.bounding_box().unwrap(); + let outer_bbox = other.bounding_box().unwrap(); + + // Eliminate this subpath if its bounding box is not completely inside the other subpath's bounding box. + // Reasoning: + // If the (min x, min y) of the inner subpath is less than or equal to the (min x, min y) of the outer subpath, + // or if the (min x, min y) of the inner subpath is greater than or equal to the (max x, max y) of the outer subpath, + // then the inner subpath is intersecting with or outside the outer subpath. The same logic applies for (max x, max y). + if !is_rectangle_inside_other(inner_bbox, outer_bbox) { + return false; + } - // Eliminate this if any of its the subpath's anchors is outside other's subpath + // Eliminate this subpath if any of its anchors are outside the other subpath. for anchors in self.anchors() { if !other.contains_point(anchors) { return false; } } - // Eliminate this if its subpath is intersecting wih the other's subpath - if !self.subpath_intersections(&other, error, minimum_separation).is_empty() { + // Eliminate this subpath if it intersects with the other subpath. + if !self.subpath_intersections(other, error, minimum_separation).is_empty() { return false; } - // here (1) this subpath bbox is inside other bbox, (2) its anchors are inside other subpath and (3) it is not intersecting with other subpath. - // hence this this subpath is completely inside given other subpath. + // 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. + // Hence, this subpath is completely inside the given other subpath. true } @@ -298,7 +307,7 @@ impl Subpath { }) } - /// Return the min and max corners that represent the bounding box of the subpath. + /// Return the min and max corners that represent the bounding box of the subpath. Return `None` if the subpath is empty. /// pub fn bounding_box(&self) -> Option<[DVec2; 2]> { self.iter().map(|bezier| bezier.bounding_box()).reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])]) @@ -909,24 +918,24 @@ mod tests { #[test] fn is_inside_subpath() { - let lasso_polygon = [DVec2::new(100., 100.), DVec2::new(500., 100.), DVec2::new(500., 500.), DVec2::new(100., 500.)].to_vec(); - let lasso_polygon = Subpath::from_anchors_linear(lasso_polygon, true); + let boundary_polygon = [DVec2::new(100., 100.), DVec2::new(500., 100.), DVec2::new(500., 500.), DVec2::new(100., 500.)].to_vec(); + let boundary_polygon = Subpath::from_anchors_linear(boundary_polygon, true); let curve = Bezier::from_quadratic_dvec2(DVec2::new(189., 289.), DVec2::new(9., 286.), DVec2::new(45., 410.)); let curve_intersecting = Subpath::::from_bezier(&curve); - assert_eq!(curve_intersecting.is_inside_subpath(&lasso_polygon, None, None), false); + assert_eq!(curve_intersecting.is_inside_subpath(&boundary_polygon, None, None), false); let curve = Bezier::from_quadratic_dvec2(DVec2::new(115., 37.), DVec2::new(51.4, 91.8), DVec2::new(76.5, 242.)); let curve_outside = Subpath::::from_bezier(&curve); - assert_eq!(curve_outside.is_inside_subpath(&lasso_polygon, None, None), false); + assert_eq!(curve_outside.is_inside_subpath(&boundary_polygon, None, None), false); let curve = Bezier::from_cubic_dvec2(DVec2::new(210.1, 133.5), DVec2::new(150.2, 436.9), DVec2::new(436., 285.), DVec2::new(247.6, 240.7)); let curve_inside = Subpath::::from_bezier(&curve); - assert_eq!(curve_inside.is_inside_subpath(&lasso_polygon, None, None), true); + assert_eq!(curve_inside.is_inside_subpath(&boundary_polygon, None, None), true); let line = Bezier::from_linear_dvec2(DVec2::new(101., 101.5), DVec2::new(150.2, 499.)); let line_inside = Subpath::::from_bezier(&line); - assert_eq!(line_inside.is_inside_subpath(&lasso_polygon, None, None), true); + assert_eq!(line_inside.is_inside_subpath(&boundary_polygon, None, None), true); } #[test] diff --git a/libraries/bezier-rs/src/utils.rs b/libraries/bezier-rs/src/utils.rs index 71612be50c..2cafd28808 100644 --- a/libraries/bezier-rs/src/utils.rs +++ b/libraries/bezier-rs/src/utils.rs @@ -171,7 +171,7 @@ pub fn solve_cubic(a: f64, b: f64, c: f64, d: f64) -> [Option; 3] { } } -/// Check if two rectangles have any overlap. The rectangles are represented by a pair of coordinates that designate the top left and bottom right corners (in a graphical coordinate system). +/// Determines if two rectangles have any overlap. The rectangles are represented by a pair of coordinates that designate the top left and bottom right corners (in a graphical coordinate system). pub fn do_rectangles_overlap(rectangle1: [DVec2; 2], rectangle2: [DVec2; 2]) -> bool { let [bottom_left1, top_right1] = rectangle1; let [bottom_left2, top_right2] = rectangle2; @@ -179,13 +179,13 @@ pub fn do_rectangles_overlap(rectangle1: [DVec2; 2], rectangle2: [DVec2; 2]) -> top_right1.x >= bottom_left2.x && top_right2.x >= bottom_left1.x && top_right2.y >= bottom_left1.y && top_right1.y >= bottom_left2.y } -/// Check if a point is completely inside rectangle which is respresented as pair of coordinates [top-left, bottom-right]. +/// Determines if a point is completely inside a rectangle, which is represented as a pair of coordinates [top-left, bottom-right]. pub fn is_point_inside_rectangle(rect: [DVec2; 2], point: DVec2) -> bool { - let [top_left, bottom_rigth] = rect; - point.x > top_left.x && point.x < bottom_rigth.x && point.y > top_left.y && point.y < bottom_rigth.y + let [top_left, bottom_right] = rect; + point.x > top_left.x && point.x < bottom_right.x && point.y > top_left.y && point.y < bottom_right.y } -/// Check if inner rectangle is completely inside outer rectangle. The rectangles are represented as pair of coordinates [top-left, bottom-right]. +/// Determines if the inner rectangle is completely inside the outer rectangle. The rectangles are represented as pairs of coordinates [top-left, bottom-right]. pub fn is_rectangle_inside_other(inner: [DVec2; 2], outer: [DVec2; 2]) -> bool { is_point_inside_rectangle(outer, inner[0]) && is_point_inside_rectangle(outer, inner[1]) }