diff --git a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs index 6b4779f0ae..9c65420191 100644 --- a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs +++ b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs @@ -1,7 +1,7 @@ use super::poisson_disk::poisson_disk_sample; use crate::vector::misc::dvec2_to_point; use glam::DVec2; -use kurbo::{Affine, BezPath, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, Rect, Shape}; +use kurbo::{BezPath, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, Rect, Shape}; /// Accuracy to find the position on [kurbo::Bezpath]. const POSITION_ACCURACY: f64 = 1e-5; @@ -198,41 +198,40 @@ 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, /// 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(bezpath: &BezPath, separation_disk_diameter: f64, rng: impl FnMut() -> f64, subpaths: &[(BezPath, Rect)], subpath_index: usize) -> Vec { - if bezpath.elements().is_empty() { +pub fn poisson_disk_points(mut current_bezpath: BezPath, separation_disk_diameter: f64, rng: impl FnMut() -> f64, bezpaths: &[(BezPath, Rect)]) -> Vec { + if current_bezpath.elements().is_empty() { return Vec::new(); } - 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 + current_bezpath.close_path(); - let mut shape = bezpath.clone(); - shape.close_path(); - shape.apply_affine(Affine::translate((-offset_x, -offset_y))); + // Get the bounding box of the bezpath after offsetting it to the origin (x: 0, y: 0). + let current_bezpath_bbox = current_bezpath.bounding_box(); + let (width, height) = (current_bezpath_bbox.width(), current_bezpath_bbox.height()); + let (offset_x, offset_y) = (current_bezpath_bbox.x0, current_bezpath_bbox.y0); let point_in_shape_checker = |point: DVec2| { // Check against all paths the point is contained in to compute the correct winding number + let point = point + DVec2::new(offset_x, offset_y); + + if !current_bezpath_bbox.contains(dvec2_to_point(point)) || !current_bezpath.contains(dvec2_to_point(point)) { + return false; + } + let mut number = 0; - for (i, (shape, bbox)) in subpaths.iter().enumerate() { - let point = point + DVec2::new(bbox.x0, bbox.y0); - if bbox.x0 > point.x || bbox.y0 > point.y || bbox.x1 < point.x || bbox.y1 < point.y { - continue; - } - let winding = shape.winding(dvec2_to_point(point)); - if i == subpath_index && winding == 0 { - return false; - } - number += winding; + for i in 0..bezpaths.len() { + let (bezpath, _) = &bezpaths[i]; + let winding = bezpath.winding(dvec2_to_point(point)); + number += winding } number != 0 }; let square_edges_intersect_shape_checker = |position: DVec2, size: f64| { let rect = Rect::new(position.x, position.y, position.x + size, position.y + size); - bezpath_rectangle_intersections_exist(bezpath, rect) + bezpath_rectangle_intersections_exist(¤t_bezpath, rect) }; let mut points = poisson_disk_sample(width, height, separation_disk_diameter, point_in_shape_checker, square_edges_intersect_shape_checker, rng); diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index aba89db824..73dde1db80 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -7,11 +7,12 @@ use super::style::{PathStyle, Stroke}; use crate::instances::Instances; use crate::{AlphaBlending, Color, GraphicGroupTable}; pub use attributes::*; -use bezier_rs::{BezierHandles, ManipulatorGroup}; +use bezier_rs::ManipulatorGroup; use core::borrow::Borrow; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; pub use indexed::VectorDataIndex; +use kurbo::PathEl; pub use modification::*; use std::collections::HashMap; @@ -179,66 +180,7 @@ impl VectorData { /// Appends a Kurbo BezPath to the vector data. pub fn append_bezpath(&mut self, bezpath: kurbo::BezPath) { - let mut first_point_index = None; - let mut last_point_index = None; - - let mut first_segment_id = None; - let mut last_segment_id = None; - - let mut point_id = self.point_domain.next_id(); - let mut segment_id = self.segment_domain.next_id(); - - let stroke_id = StrokeId::ZERO; - let fill_id = FillId::ZERO; - - for element in bezpath.elements() { - match *element { - kurbo::PathEl::MoveTo(point) => { - let next_point_index = self.point_domain.ids().len(); - self.point_domain.push(point_id.next_id(), point_to_dvec2(point)); - first_point_index = Some(next_point_index); - last_point_index = Some(next_point_index); - } - kurbo::PathEl::ClosePath => match (first_point_index, last_point_index) { - (Some(first_point_index), Some(last_point_index)) => { - let next_segment_id = segment_id.next_id(); - self.segment_domain.push(next_segment_id, first_point_index, last_point_index, BezierHandles::Linear, stroke_id); - - let next_region_id = self.region_domain.next_id(); - self.region_domain.push(next_region_id, first_segment_id.unwrap()..=next_segment_id, fill_id); - } - _ => { - error!("Empty bezpath cannot be closed.") - } - }, - _ => {} - } - - let mut append_path_element = |handle: BezierHandles, point: kurbo::Point| { - let next_point_index = self.point_domain.ids().len(); - self.point_domain.push(point_id.next_id(), point_to_dvec2(point)); - - let next_segment_id = segment_id.next_id(); - self.segment_domain.push(segment_id.next_id(), last_point_index.unwrap(), next_point_index, handle, stroke_id); - - last_point_index = Some(next_point_index); - first_segment_id = Some(first_segment_id.unwrap_or(next_segment_id)); - last_segment_id = Some(next_segment_id); - }; - - match *element { - kurbo::PathEl::LineTo(point) => append_path_element(BezierHandles::Linear, point), - kurbo::PathEl::QuadTo(handle, point) => append_path_element(BezierHandles::Quadratic { handle: point_to_dvec2(handle) }, point), - kurbo::PathEl::CurveTo(handle_start, handle_end, point) => append_path_element( - BezierHandles::Cubic { - handle_start: point_to_dvec2(handle_start), - handle_end: point_to_dvec2(handle_end), - }, - point, - ), - _ => {} - } - } + AppendBezpath::append_bezpath(self, bezpath); } /// Construct some new vector data from subpaths with an identity transform and black fill. diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 2df21db0d4..9fe4883973 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -5,6 +5,7 @@ use crate::uuid::generate_uuid; use bezier_rs::BezierHandles; use core::hash::BuildHasher; use dyn_any::DynAny; +use kurbo::BezPath; use std::collections::{HashMap, HashSet}; /// Represents a procedural change to the [`PointDomain`] in [`VectorData`]. @@ -552,3 +553,92 @@ where let visitor = HashMapVisitor { marker: std::marker::PhantomData }; deserializer.deserialize_seq(visitor) } + +pub struct AppendBezpath<'a> { + first_point_index: Option, + last_point_index: Option, + first_segment_id: Option, + last_segment_id: Option, + next_handle: Option, + point_id: PointId, + segment_id: SegmentId, + vector_data: &'a mut VectorData, +} + +impl<'a> AppendBezpath<'a> { + fn new(vector_data: &'a mut VectorData) -> Self { + Self { + first_point_index: None, + last_point_index: None, + first_segment_id: None, + last_segment_id: None, + next_handle: None, + point_id: vector_data.point_domain.next_id(), + segment_id: vector_data.segment_domain.next_id(), + vector_data, + } + } + + fn append_path_element(&mut self, handle: BezierHandles, point: kurbo::Point, next_element: Option<&PathEl>) { + if let Some(PathEl::ClosePath) = next_element { + self.next_handle = Some(handle); + } else { + let next_point_index = self.vector_data.point_domain.ids().len(); + self.vector_data.point_domain.push(self.point_id.next_id(), point_to_dvec2(point)); + + let next_segment_id = self.segment_id.next_id(); + self.vector_data + .segment_domain + .push(self.segment_id.next_id(), self.last_point_index.unwrap(), next_point_index, handle, StrokeId::ZERO); + + self.last_point_index = Some(next_point_index); + self.first_segment_id = Some(self.first_segment_id.unwrap_or(next_segment_id)); + self.last_segment_id = Some(next_segment_id); + } + } + + pub fn append_bezpath(vector_data: &'a mut VectorData, bezpath: BezPath) { + let mut this = Self::new(vector_data); + + let stroke_id = StrokeId::ZERO; + let fill_id = FillId::ZERO; + + for i in 0..bezpath.elements().len() { + let current_element = bezpath.elements()[i]; + let next_element = bezpath.elements().get(i + 1); + + match current_element { + kurbo::PathEl::MoveTo(point) => { + let next_point_index = this.vector_data.point_domain.ids().len(); + this.vector_data.point_domain.push(this.point_id.next_id(), point_to_dvec2(point)); + this.first_point_index = Some(next_point_index); + this.last_point_index = Some(next_point_index); + } + kurbo::PathEl::ClosePath => match (this.first_point_index, this.last_point_index) { + (Some(first_point_index), Some(last_point_index)) => { + let next_segment_id = this.segment_id.next_id(); + this.vector_data + .segment_domain + .push(next_segment_id, last_point_index, first_point_index, this.next_handle.unwrap_or(BezierHandles::Linear), stroke_id); + + let next_region_id = this.vector_data.region_domain.next_id(); + this.vector_data.region_domain.push(next_region_id, this.first_segment_id.unwrap()..=next_segment_id, fill_id); + } + _ => { + error!("Empty bezpath cannot be closed.") + } + }, + kurbo::PathEl::LineTo(point) => this.append_path_element(BezierHandles::Linear, point, next_element), + kurbo::PathEl::QuadTo(handle, point) => this.append_path_element(BezierHandles::Quadratic { handle: point_to_dvec2(handle) }, point, next_element), + kurbo::PathEl::CurveTo(handle_start, handle_end, point) => this.append_path_element( + BezierHandles::Cubic { + handle_start: point_to_dvec2(handle_start), + handle_end: point_to_dvec2(handle_end), + }, + point, + next_element, + ), + } + } + } +} diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index c717988a4c..e88fba4606 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -1364,24 +1364,26 @@ async fn poisson_disk_points( if separation_disk_diameter <= 0.01 { return VectorDataTable::new(result); } - let path_with_bounding_boxes: Vec<_> = vector_data + + let bezpaths: Vec<_> = vector_data .stroke_bezpath_iter() - .map(|mut subpath| { + .map(|mut bezpath| { // TODO: apply transform to points instead of modifying the paths - subpath.apply_affine(Affine::new(vector_data_transform.to_cols_array())); - let bbox = subpath.bounding_box(); - (subpath, bbox) + bezpath.apply_affine(Affine::new(vector_data_transform.to_cols_array())); + let bbox = bezpath.bounding_box(); + (bezpath, bbox) }) .collect(); - for (i, (subpath, _)) in path_with_bounding_boxes.iter().enumerate() { - if subpath.segments().count() < 2 { + for i in 0..bezpaths.len() { + let bezpath = bezpaths[i].0.clone(); + if bezpath.segments().count() < 2 { continue; } let mut poisson_disk_bezpath = BezPath::new(); - for point in bezpath_algorithms::poisson_disk_points(subpath, separation_disk_diameter, || rng.random::(), &path_with_bounding_boxes, i) { + for point in bezpath_algorithms::poisson_disk_points(bezpath, separation_disk_diameter, || rng.random::(), &bezpaths) { if poisson_disk_bezpath.elements().is_empty() { poisson_disk_bezpath.move_to(dvec2_to_point(point)); } else {