mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-06 22:48:12 +08:00
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:
@@ -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
|
||||
))
|
||||
}
|
||||
|
||||
@@ -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]> {
|
||||
|
||||
Reference in New Issue
Block a user