Add Poisson-disk sampling node and Bezier-rs 0.4 release (#1586)

* Add Poisson-disk sampling node and Bezier-rs 0.4 release

* Additional optimizations

* More performance optimizations with help from 0Hypercube

* Add comments
This commit is contained in:
Keavon Chambers
2024-01-28 02:25:46 -08:00
committed by GitHub
parent a7bf6e2459
commit 6b6accfb91
21 changed files with 778 additions and 50 deletions
+7 -7
View File
@@ -127,10 +127,10 @@ impl Bezier {
pub fn write_curve_argument(&self, svg: &mut String) -> std::fmt::Result {
match self.handles {
BezierHandles::Linear => svg.push_str(SVG_ARG_LINEAR),
BezierHandles::Quadratic { handle } => write!(svg, "{SVG_ARG_QUADRATIC}{},{}", handle.x, handle.y)?,
BezierHandles::Cubic { handle_start, handle_end } => write!(svg, "{SVG_ARG_CUBIC}{},{} {},{}", handle_start.x, handle_start.y, handle_end.x, handle_end.y)?,
BezierHandles::Quadratic { handle } => write!(svg, "{SVG_ARG_QUADRATIC}{:.6},{:.6}", handle.x, handle.y)?,
BezierHandles::Cubic { handle_start, handle_end } => write!(svg, "{SVG_ARG_CUBIC}{:.6},{:.6} {:.6},{:.6}", handle_start.x, handle_start.y, handle_end.x, handle_end.y)?,
}
write!(svg, " {},{}", self.end.x, self.end.y)
write!(svg, " {:.6},{:.6}", self.end.x, self.end.y)
}
/// Return the string argument used to create the lines connecting handles to endpoints in an SVG `path`
@@ -138,17 +138,17 @@ impl Bezier {
match self.handles {
BezierHandles::Linear => None,
BezierHandles::Quadratic { handle } => {
let handle_line = format!("{SVG_ARG_LINEAR}{} {}", handle.x, handle.y);
let handle_line = format!("{SVG_ARG_LINEAR}{:.6} {:.6}", handle.x, handle.y);
Some(format!(
"{SVG_ARG_MOVE}{} {} {handle_line} {SVG_ARG_MOVE}{} {} {handle_line}",
"{SVG_ARG_MOVE}{:.6} {:.6} {handle_line} {SVG_ARG_MOVE}{:.6} {:.6} {handle_line}",
self.start.x, self.start.y, self.end.x, self.end.y
))
}
BezierHandles::Cubic { handle_start, handle_end } => {
let handle_start_line = format!("{SVG_ARG_LINEAR}{} {}", handle_start.x, handle_start.y);
let handle_start_line = format!("{SVG_ARG_LINEAR}{:.6} {:.6}", handle_start.x, handle_start.y);
let handle_end_line = format!("{SVG_ARG_LINEAR}{} {}", handle_end.x, handle_end.y);
Some(format!(
"{SVG_ARG_MOVE}{} {} {handle_start_line} {SVG_ARG_MOVE}{} {} {handle_end_line}",
"{SVG_ARG_MOVE}{:.6} {:.6} {handle_start_line} {SVG_ARG_MOVE}{:.6} {:.6} {handle_end_line}",
self.start.x, self.start.y, self.end.x, self.end.y
))
}
+68 -7
View File
@@ -181,6 +181,15 @@ impl Bezier {
[endpoints_min, endpoints_max]
}
/// Return the min and max corners that represent the bounding box enclosing this Bezier's two anchor points and any handles.
pub fn bounding_box_of_anchors_and_handles(&self) -> [DVec2; 2] {
match self.handles {
BezierHandles::Linear => [self.start.min(self.end), self.start.max(self.end)],
BezierHandles::Quadratic { handle } => [self.start.min(self.end).min(handle), self.start.max(self.end).max(handle)],
BezierHandles::Cubic { handle_start, handle_end } => [self.start.min(self.end).min(handle_start).min(handle_end), self.start.max(self.end).max(handle_start).max(handle_end)],
}
}
/// Returns `true` if the bounding box of the bezier is contained entirely within a rectangle defined by its minimum and maximum corners.
pub fn is_contained_within(&self, min_corner: DVec2, max_corner: DVec2) -> bool {
let [bounding_box_min, bounding_box_max] = self.bounding_box();
@@ -261,7 +270,7 @@ impl Bezier {
/// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`.
/// <iframe frameBorder="0" width="100%" height="300px" src="https://graphite.rs/libraries/bezier-rs#bezier/inflections/solo" title="Inflections Demo"></iframe>
pub fn inflections(&self) -> Vec<f64> {
self.unrestricted_inflections().into_iter().filter(|&t| t > 0. && t < 1.).collect::<Vec<f64>>()
self.unrestricted_inflections().filter(|&t| t > 0. && t < 1.).collect::<Vec<f64>>()
}
/// Implementation of the algorithm to find curve intersections by iterating on bounding boxes.
@@ -343,22 +352,21 @@ impl Bezier {
let line_directional_vector = other.end - other.start;
let angle = line_directional_vector.angle_between(DVec2::new(0., 1.));
let rotation_matrix = DMat2::from_angle(angle);
let rotated_bezier = self.apply_transformation(|point| rotation_matrix.mul_vec2(point));
let rotated_line = [rotation_matrix.mul_vec2(other.start), rotation_matrix.mul_vec2(other.end)];
let rotated_bezier = self.apply_transformation(|point| rotation_matrix * point);
// Translate the bezier such that the line becomes aligned on top of the x-axis
let vertical_distance = rotated_line[0].x;
let vertical_distance = (rotation_matrix * other.start).x;
let translated_bezier = rotated_bezier.translate(DVec2::new(-vertical_distance, 0.));
// Compute the roots of the resulting bezier curve
let list_intersection_t = translated_bezier.find_tvalues_for_x(0.);
let min = other.start.min(other.end);
let max = other.start.max(other.end);
// Calculate line's bounding box
let [min_corner, max_corner] = other.bounding_box_of_anchors_and_handles();
return list_intersection_t
// Accept the t value if it is approximately in [0, 1] and if the corresponding coordinates are within the range of the linear line
.filter(|&t| utils::dvec2_approximately_in_range(self.unrestricted_parametric_evaluate(t), min, max, MAX_ABSOLUTE_DIFFERENCE).all())
.filter(|&t| utils::dvec2_approximately_in_range(self.unrestricted_parametric_evaluate(t), min_corner, max_corner, MAX_ABSOLUTE_DIFFERENCE).all())
// Ensure the returned value is within the correct range
.map(|t| t.clamp(0., 1.))
.collect::<Vec<f64>>();
@@ -369,6 +377,59 @@ impl Bezier {
self.intersections_between_subcurves(0. ..1., other, 0. ..1., error).iter().map(|t_values| t_values[0]).collect()
}
/// Returns a list of `t` values that correspond to points on this Bezier segment where they intersect with the given line. (`direction_vector` does not need to be normalized.)
/// If this needs to be called frequently with a line of the same rotation angle, consider instead using [`line_test_crossings_prerotated`] and moving this function's setup code into your own logic before the repeated call.
pub fn line_test_crossings(&self, point_on_line: DVec2, direction_vector: DVec2) -> impl Iterator<Item = f64> + '_ {
// Rotate the bezier and the line by the angle that the line makes with the x axis
let angle = direction_vector.angle_between(DVec2::new(0., 1.));
let rotation_matrix = DMat2::from_angle(angle);
let rotated_bezier = self.apply_transformation(|point| rotation_matrix * point);
self.line_test_crossings_prerotated(point_on_line, rotation_matrix, rotated_bezier)
}
/// Returns a list of `t` values that correspond to points on this Bezier segment where they intersect with the given infinite line.
/// This version of the function is for better performance when calling it frequently without needing to change the rotation between each call.
/// If that isn't important, use [`line_test_crossings`] which wraps this and provides an easier interface by taking a line rotation vector.
/// Instead, this version requires a rotation matrix for the line's rotation and a version of this Bezier segment that has had its rotation already applied.
pub fn line_test_crossings_prerotated(&self, point_on_line: DVec2, rotation_matrix: DMat2, rotated_bezier: Self) -> impl Iterator<Item = f64> + '_ {
// Translate the bezier such that the line becomes aligned on top of the x-axis
let vertical_distance = (rotation_matrix.x_axis.x * point_on_line.x) + (rotation_matrix.y_axis.x * point_on_line.y);
let translated_bezier = rotated_bezier.translate(DVec2::new(-vertical_distance, 0.));
// Compute the roots of the resulting bezier curve
translated_bezier.find_tvalues_for_x(0.)
}
/// Returns a list of `t` values that correspond to points on this Bezier segment where they intersect with the given ray. (`ray_direction` does not need to be normalized.)
/// If this needs to be called frequently with a ray of the same rotation angle, consider instead using [`ray_test_crossings_prerotated`] and moving this function's setup code into your own logic before the repeated call.
pub fn ray_test_crossings(&self, ray_start: DVec2, ray_direction: DVec2) -> impl Iterator<Item = f64> + '_ {
// Rotate the bezier and the line by the angle that the line makes with the x axis
let angle = ray_direction.angle_between(DVec2::new(0., 1.));
let rotation_matrix = DMat2::from_angle(angle);
let rotated_bezier = self.apply_transformation(|point| rotation_matrix * point);
self.ray_test_crossings_prerotated(ray_start, rotation_matrix, rotated_bezier)
}
/// Returns a list of `t` values that correspond to points on this Bezier segment where they intersect with the given infinite ray.
/// This version of the function is for better performance when calling it frequently without needing to change the rotation between each call.
/// If that isn't important, use [`ray_test_crossings`] which wraps this and provides an easier interface by taking a ray direction vector.
/// Instead, this version requires a rotation matrix for the ray's rotation and a version of this Bezier segment that has had its rotation already applied.
pub fn ray_test_crossings_prerotated(&self, ray_start: DVec2, rotation_matrix: DMat2, rotated_bezier: Self) -> impl Iterator<Item = f64> + '_ {
// Intersection t-values include those beyond the [0-1] range where the segment's ends extend through the X-axis
let intersection_t_values_on_rotated_bezier = self.line_test_crossings_prerotated(ray_start, rotation_matrix, rotated_bezier);
intersection_t_values_on_rotated_bezier
// Accept the t value if it is approximately in [0, 1] and if the corresponding coordinates are within the range of the linear line
.filter(move |&t| {
let point = self.unrestricted_parametric_evaluate(t);
// Ensure the returned value is within the correct range
let in_bounds = point.cmpge(ray_start) | utils::dvec2_compare(point, ray_start, MAX_ABSOLUTE_DIFFERENCE);
in_bounds.x && in_bounds.y
})
}
/// Helper function to compute intersections between lists of subcurves.
/// This function uses the algorithm implemented in `intersections_between_subcurves`.
fn intersections_between_vectors_of_curves(subcurves1: &[(Bezier, Range<f64>)], subcurves2: &[(Bezier, Range<f64>)], error: f64) -> Vec<[f64; 2]> {