Bezier-rs: Add calculations for area and centroid of subpaths (#1729)

* add code to calculate area and centroid of subpath

* change library to poly_it

* add a demo of area and centroid

* modify algorithm to consider negetive area as positive

* add code for manipulating polynomials in bezier-rs

* remove `poly_it` dependency and use custom Polynomial

* formatting floats to skip last zero

* add debug mechanism

* collect both intersection points instead of one

* fix test and cargo fmt

* apply minimum separation filtering in self_intersection and use better endpoint filtering algorithm

* remove debug mechanism and cargo fmt

* consider the subpath as closed for intersection calculation

* add documentation for polynomial.rs

* impl display for Polynomial

* make area always positive

* add missing docs

* fix test and cargo fmt

* Naming/formatting code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Elbert Ronnie
2024-04-29 19:19:12 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent e769f50877
commit beb88d280c
11 changed files with 577 additions and 15 deletions
+31 -1
View File
@@ -116,7 +116,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
let err = error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);
// TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath collection
self.iter().enumerate().for_each(|(i, other)| {
intersections_vec.extend(other.self_intersections(error).iter().map(|value| (i, value[0])));
intersections_vec.extend(other.self_intersections(error, minimum_separation).iter().map(|value| (i, value[0])));
self.iter().enumerate().skip(i + 1).for_each(|(j, curve)| {
intersections_vec.extend(
curve
@@ -130,6 +130,36 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
intersections_vec
}
/// Returns a list of `t` values that correspond to all the self intersection points of the subpath always considering it as a closed subpath. The index and `t` value of both will be returned that corresponds to a point.
/// The points will be sorted based on their index and `t` repsectively.
/// - `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 two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.
/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two
///
/// **NOTE**: if an intersection were to occur within an `error` distance away from an anchor point, the algorithm will filter that intersection out.
pub fn all_self_intersections(&self, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {
let mut intersections_vec = Vec::new();
let err = error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);
let num_curves = self.len();
// TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath collection
self.iter_closed().enumerate().for_each(|(i, other)| {
intersections_vec.extend(other.self_intersections(error, minimum_separation).iter().flat_map(|value| [(i, value[0]), (i, value[1])]));
self.iter_closed().enumerate().skip(i + 1).for_each(|(j, curve)| {
intersections_vec.extend(
curve
.all_intersections(&other, error, minimum_separation)
.iter()
.filter(|&value| (j != i + 1 || value[0] > err || (1. - value[1]) > err) && (j != num_curves - 1 || i != 0 || value[1] > err || (1. - value[0]) > err))
.flat_map(|value| [(j, value[0]), (i, value[1])]),
);
});
});
intersections_vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
intersections_vec
}
/// Calculates the intersection points the subpath has with a given rectangle and returns a list of `(usize, f64)` tuples,
/// where the `usize` represents the index of the curve in the subpath, and the `f64` represents the `t`-value local to
/// that curve where the intersection occurred.