Refactor the 'Scatter Points' node to use Kurbo instead of Bezier-rs (#2634)
* it works! * clean up * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
5861e635fb
commit
54b4ef145c
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,7 @@
|
||||||
use super::poisson_disk::poisson_disk_sample;
|
use super::poisson_disk::poisson_disk_sample;
|
||||||
use crate::vector::PointId;
|
use crate::vector::misc::dvec2_to_point;
|
||||||
use bezier_rs::Subpath;
|
use glam::DVec2;
|
||||||
use glam::{DAffine2, DVec2};
|
use kurbo::{Affine, BezPath, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, Rect, Shape};
|
||||||
use kurbo::{BezPath, ParamCurve, ParamCurveDeriv, PathSeg, Point, Shape};
|
|
||||||
|
|
||||||
/// Accuracy to find the position on [kurbo::Bezpath].
|
/// Accuracy to find the position on [kurbo::Bezpath].
|
||||||
const POSITION_ACCURACY: f64 = 1e-5;
|
const POSITION_ACCURACY: f64 = 1e-5;
|
||||||
|
|
@ -199,26 +198,29 @@ fn bezpath_t_value_to_parametric(bezpath: &kurbo::BezPath, t: BezPathTValue, pre
|
||||||
///
|
///
|
||||||
/// While the conceptual process described above asymptotically slows down and is never guaranteed to produce a maximal set in finite time,
|
/// While the conceptual process described above asymptotically slows down and is never guaranteed to produce a maximal set in finite time,
|
||||||
/// this is implemented with an algorithm that produces a maximal set in O(n) time. The slowest part is actually checking if points are inside the subpath shape.
|
/// this is implemented with an algorithm that produces a maximal set in O(n) time. The slowest part is actually checking if points are inside the subpath shape.
|
||||||
pub fn poisson_disk_points(this: &Subpath<PointId>, separation_disk_diameter: f64, rng: impl FnMut() -> f64, subpaths: &[(Subpath<PointId>, [DVec2; 2])], subpath_index: usize) -> Vec<DVec2> {
|
pub fn poisson_disk_points(bezpath: &BezPath, separation_disk_diameter: f64, rng: impl FnMut() -> f64, subpaths: &[(BezPath, Rect)], subpath_index: usize) -> Vec<DVec2> {
|
||||||
let Some(bounding_box) = this.bounding_box() else { return Vec::new() };
|
if bezpath.elements().is_empty() {
|
||||||
let (offset_x, offset_y) = bounding_box[0].into();
|
return Vec::new();
|
||||||
let (width, height) = (bounding_box[1] - bounding_box[0]).into();
|
}
|
||||||
|
let bbox = bezpath.bounding_box();
|
||||||
|
let (offset_x, offset_y) = (bbox.x0, bbox.y0);
|
||||||
|
let (width, height) = (bbox.x1 - bbox.x0, bbox.y1 - bbox.y0);
|
||||||
|
|
||||||
// TODO: Optimize the following code and make it more robust
|
// TODO: Optimize the following code and make it more robust
|
||||||
|
|
||||||
let mut shape = this.clone();
|
let mut shape = bezpath.clone();
|
||||||
shape.set_closed(true);
|
shape.close_path();
|
||||||
shape.apply_transform(DAffine2::from_translation((-offset_x, -offset_y).into()));
|
shape.apply_affine(Affine::translate((-offset_x, -offset_y)));
|
||||||
|
|
||||||
let point_in_shape_checker = |point: DVec2| {
|
let point_in_shape_checker = |point: DVec2| {
|
||||||
// Check against all paths the point is contained in to compute the correct winding number
|
// Check against all paths the point is contained in to compute the correct winding number
|
||||||
let mut number = 0;
|
let mut number = 0;
|
||||||
for (i, (shape, bb)) in subpaths.iter().enumerate() {
|
for (i, (shape, bbox)) in subpaths.iter().enumerate() {
|
||||||
let point = point + bounding_box[0];
|
let point = point + DVec2::new(bbox.x0, bbox.y0);
|
||||||
if bb[0].x > point.x || bb[0].y > point.y || bb[1].x < point.x || bb[1].y < point.y {
|
if bbox.x0 > point.x || bbox.y0 > point.y || bbox.x1 < point.x || bbox.y1 < point.y {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let winding = shape.winding_order(point);
|
let winding = shape.winding(dvec2_to_point(point));
|
||||||
|
|
||||||
if i == subpath_index && winding == 0 {
|
if i == subpath_index && winding == 0 {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -228,9 +230,9 @@ pub fn poisson_disk_points(this: &Subpath<PointId>, separation_disk_diameter: f6
|
||||||
number != 0
|
number != 0
|
||||||
};
|
};
|
||||||
|
|
||||||
let square_edges_intersect_shape_checker = |corner1: DVec2, size: f64| {
|
let square_edges_intersect_shape_checker = |position: DVec2, size: f64| {
|
||||||
let corner2 = corner1 + DVec2::splat(size);
|
let rect = Rect::new(position.x, position.y, position.x + size, position.y + size);
|
||||||
this.rectangle_intersections_exist(corner1, corner2)
|
bezpath_rectangle_intersections_exist(bezpath, rect)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut points = poisson_disk_sample(width, height, separation_disk_diameter, point_in_shape_checker, square_edges_intersect_shape_checker, rng);
|
let mut points = poisson_disk_sample(width, height, separation_disk_diameter, point_in_shape_checker, square_edges_intersect_shape_checker, rng);
|
||||||
|
|
@ -240,3 +242,33 @@ pub fn poisson_disk_points(this: &Subpath<PointId>, separation_disk_diameter: f6
|
||||||
}
|
}
|
||||||
points
|
points
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bezpath_rectangle_intersections_exist(bezpath: &BezPath, rect: Rect) -> bool {
|
||||||
|
if !bezpath.bounding_box().overlaps(rect) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top left
|
||||||
|
let p1 = Point::new(rect.x0, rect.y0);
|
||||||
|
// Top right
|
||||||
|
let p2 = Point::new(rect.x1, rect.y0);
|
||||||
|
// Bottom right
|
||||||
|
let p3 = Point::new(rect.x1, rect.y1);
|
||||||
|
// Bottom left
|
||||||
|
let p4 = Point::new(rect.x0, rect.y1);
|
||||||
|
|
||||||
|
let top_line = Line::new((p1.x, p1.y), (p2.x, p2.y));
|
||||||
|
let right_line = Line::new((p2.x, p2.y), (p3.x, p3.y));
|
||||||
|
let bottom_line = Line::new((p3.x, p3.y), (p4.x, p4.y));
|
||||||
|
let left_line = Line::new((p4.x, p4.y), (p1.x, p1.y));
|
||||||
|
|
||||||
|
for segment in bezpath.segments() {
|
||||||
|
for line in [top_line, right_line, bottom_line, left_line] {
|
||||||
|
if !segment.intersect_line(line).is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use super::algorithms::bezpath_algorithms::{PERIMETER_ACCURACY, position_on_bezpath, sample_points_on_bezpath, tangent_on_bezpath};
|
use super::algorithms::bezpath_algorithms::{self, PERIMETER_ACCURACY, position_on_bezpath, sample_points_on_bezpath, tangent_on_bezpath};
|
||||||
use super::algorithms::offset_subpath::offset_subpath;
|
use super::algorithms::offset_subpath::offset_subpath;
|
||||||
use super::misc::{CentroidType, point_to_dvec2};
|
use super::misc::{CentroidType, point_to_dvec2};
|
||||||
use super::style::{Fill, Gradient, GradientStops, Stroke};
|
use super::style::{Fill, Gradient, GradientStops, Stroke};
|
||||||
|
|
@ -9,13 +9,14 @@ use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier,
|
||||||
use crate::renderer::GraphicElementRendered;
|
use crate::renderer::GraphicElementRendered;
|
||||||
use crate::transform::{Footprint, ReferencePoint, Transform, TransformMut};
|
use crate::transform::{Footprint, ReferencePoint, Transform, TransformMut};
|
||||||
use crate::vector::PointDomain;
|
use crate::vector::PointDomain;
|
||||||
|
use crate::vector::misc::dvec2_to_point;
|
||||||
use crate::vector::style::{LineCap, LineJoin};
|
use crate::vector::style::{LineCap, LineJoin};
|
||||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
||||||
use bezier_rs::{Join, ManipulatorGroup, Subpath, SubpathTValue};
|
use bezier_rs::{Join, ManipulatorGroup, Subpath, SubpathTValue};
|
||||||
use core::f64::consts::PI;
|
use core::f64::consts::PI;
|
||||||
use core::hash::{Hash, Hasher};
|
use core::hash::{Hash, Hasher};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use kurbo::{Affine, Shape};
|
use kurbo::{Affine, BezPath, Shape};
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
|
||||||
|
|
@ -1364,36 +1365,30 @@ async fn poisson_disk_points(
|
||||||
return VectorDataTable::new(result);
|
return VectorDataTable::new(result);
|
||||||
}
|
}
|
||||||
let path_with_bounding_boxes: Vec<_> = vector_data
|
let path_with_bounding_boxes: Vec<_> = vector_data
|
||||||
.stroke_bezier_paths()
|
.stroke_bezpath_iter()
|
||||||
.filter_map(|mut subpath| {
|
.map(|mut subpath| {
|
||||||
// TODO: apply transform to points instead of modifying the paths
|
// TODO: apply transform to points instead of modifying the paths
|
||||||
subpath.apply_transform(vector_data_transform);
|
subpath.apply_affine(Affine::new(vector_data_transform.to_cols_array()));
|
||||||
subpath.loose_bounding_box().map(|bb| (subpath, bb))
|
let bbox = subpath.bounding_box();
|
||||||
|
(subpath, bbox)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for (i, (subpath, _)) in path_with_bounding_boxes.iter().enumerate() {
|
for (i, (subpath, _)) in path_with_bounding_boxes.iter().enumerate() {
|
||||||
if subpath.manipulator_groups().len() < 3 {
|
if subpath.segments().count() < 2 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut previous_point_index: Option<usize> = None;
|
let mut poisson_disk_bezpath = BezPath::new();
|
||||||
|
|
||||||
for point in subpath.poisson_disk_points(separation_disk_diameter, || rng.random::<f64>(), &path_with_bounding_boxes, i) {
|
for point in bezpath_algorithms::poisson_disk_points(subpath, separation_disk_diameter, || rng.random::<f64>(), &path_with_bounding_boxes, i) {
|
||||||
let point_id = PointId::generate();
|
if poisson_disk_bezpath.elements().is_empty() {
|
||||||
result.point_domain.push(point_id, point);
|
poisson_disk_bezpath.move_to(dvec2_to_point(point));
|
||||||
|
} else {
|
||||||
// Get the index of the newly added point.
|
poisson_disk_bezpath.line_to(dvec2_to_point(point));
|
||||||
let point_index = result.point_domain.ids().len() - 1;
|
|
||||||
|
|
||||||
// If there is a previous point, connect it with the current point by adding a segment.
|
|
||||||
if let Some(prev_point_index) = previous_point_index {
|
|
||||||
let segment_id = SegmentId::generate();
|
|
||||||
result.segment_domain.push(segment_id, prev_point_index, point_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
previous_point_index = Some(point_index);
|
|
||||||
}
|
}
|
||||||
|
result.append_bezpath(poisson_disk_bezpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer the style from the input vector data to the result.
|
// Transfer the style from the input vector data to the result.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue