From b9c966cbbdb2cfaee8adf98774830519c989c7ea Mon Sep 17 00:00:00 2001 From: indierusty Date: Thu, 22 May 2025 11:11:05 +0530 Subject: [PATCH 1/5] use kurbo's default accuracy constant --- .../src/vector/algorithms/bezpath_algorithms.rs | 13 ++++--------- node-graph/gcore/src/vector/vector_nodes.rs | 4 ++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs index d985ae8fda..9f0a19c9c3 100644 --- a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs +++ b/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs @@ -1,12 +1,7 @@ use super::poisson_disk::poisson_disk_sample; use crate::vector::misc::dvec2_to_point; use glam::DVec2; -use kurbo::{BezPath, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, Rect, Shape}; - -/// Accuracy to find the position on [kurbo::Bezpath]. -const POSITION_ACCURACY: f64 = 1e-5; -/// Accuracy to find the length of the [kurbo::PathSeg]. -pub const PERIMETER_ACCURACY: f64 = 1e-5; +use kurbo::{BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, Rect, Shape}; pub fn position_on_bezpath(bezpath: &BezPath, t: f64, euclidian: bool, segments_length: Option<&[f64]>) -> Point { let (segment_index, t) = t_value_to_parametric(bezpath, t, euclidian, segments_length); @@ -79,7 +74,7 @@ pub fn sample_points_on_bezpath(bezpath: BezPath, spacing: f64, start_offset: f6 let t = (next_length / next_segment_length).clamp(0., 1.); let segment = bezpath.get_seg(next_segment_index + 1).unwrap(); - let t = eval_pathseg_euclidean(segment, t, POSITION_ACCURACY); + let t = eval_pathseg_euclidean(segment, t, DEFAULT_ACCURACY); let point = segment.eval(t); if sample_bezpath.elements().is_empty() { @@ -96,7 +91,7 @@ pub fn t_value_to_parametric(bezpath: &BezPath, t: f64, euclidian: bool, segment if euclidian { let (segment_index, t) = bezpath_t_value_to_parametric(bezpath, BezPathTValue::GlobalEuclidean(t), segments_length); let segment = bezpath.get_seg(segment_index + 1).unwrap(); - return (segment_index, eval_pathseg_euclidean(segment, t, POSITION_ACCURACY)); + return (segment_index, eval_pathseg_euclidean(segment, t, DEFAULT_ACCURACY)); } bezpath_t_value_to_parametric(bezpath, BezPathTValue::GlobalParametric(t), segments_length) } @@ -164,7 +159,7 @@ fn bezpath_t_value_to_parametric(bezpath: &kurbo::BezPath, t: BezPathTValue, pre let segments_length = if let Some(segments_length) = precomputed_segments_length { segments_length } else { - computed_segments_length = bezpath.segments().map(|segment| segment.perimeter(PERIMETER_ACCURACY)).collect::>(); + computed_segments_length = bezpath.segments().map(|segment| segment.perimeter(DEFAULT_ACCURACY)).collect::>(); computed_segments_length.as_slice() }; diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index c2f9cab807..ab5225759e 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -1,4 +1,4 @@ -use super::algorithms::bezpath_algorithms::{self, PERIMETER_ACCURACY, position_on_bezpath, sample_points_on_bezpath, tangent_on_bezpath}; +use super::algorithms::bezpath_algorithms::{self, position_on_bezpath, sample_points_on_bezpath, tangent_on_bezpath}; use super::algorithms::offset_subpath::offset_subpath; use super::misc::{CentroidType, point_to_dvec2}; use super::style::{Fill, Gradient, GradientStops, Stroke}; @@ -1408,7 +1408,7 @@ async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> V .stroke_bezpath_iter() .flat_map(|mut bezpath| { bezpath.apply_affine(Affine::new(vector_data_transform.to_cols_array())); - bezpath.segments().map(|segment| segment.perimeter(PERIMETER_ACCURACY)).collect::>() + bezpath.segments().map(|segment| segment.perimeter(kurbo::DEFAULT_ACCURACY)).collect::>() }) .collect() } From 88f77f98ab77eeb05b85b844a43e01093fd52627 Mon Sep 17 00:00:00 2001 From: indierusty Date: Sun, 25 May 2025 12:17:38 +0530 Subject: [PATCH 2/5] fix append_bezpath() method --- .../src/vector/vector_data/modification.rs | 113 +++++++++--------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index aae7ed6544..b18416b4b4 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -5,7 +5,7 @@ use crate::uuid::generate_uuid; use bezier_rs::BezierHandles; use core::hash::BuildHasher; use dyn_any::DynAny; -use kurbo::{BezPath, PathEl}; +use kurbo::{BezPath, PathEl, PathSeg, Point}; use std::collections::{HashMap, HashSet}; /// Represents a procedural change to the [`PointDomain`] in [`VectorData`]. @@ -555,11 +555,11 @@ where } pub struct AppendBezpath<'a> { + first_point: Option, 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, @@ -568,79 +568,84 @@ pub struct AppendBezpath<'a> { impl<'a> AppendBezpath<'a> { fn new(vector_data: &'a mut VectorData) -> Self { Self { + first_point: None, 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); + fn append_segment(&mut self, start_point: Point, end_point: Point, handle: BezierHandles, close_path: bool) { + if self.first_point.is_none() { + self.append_first_point(start_point); + } + + let next_point_index = if let (Some(first_point_index), true) = (self.first_point_index, close_path) { + first_point_index } 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.vector_data.point_domain.push(self.point_id.next_id(), point_to_dvec2(end_point)); + next_point_index + }; + + 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); + } - 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); - } + fn append_first_point(&mut self, point: Point) { + self.first_point = Some(point); + 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)); + self.first_point_index = Some(next_point_index); + self.last_point_index = Some(next_point_index); } 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; + let is_closed = bezpath.elements().last().is_some_and(|elm| *elm == PathEl::ClosePath); + let segments = bezpath.segments().collect::>(); - for i in 0..bezpath.elements().len() { - let current_element = bezpath.elements()[i]; - let next_element = bezpath.elements().get(i + 1); + for i in 0..segments.len() { + let is_last_segment = i + 1 == segments.len(); + let close_bezpath = is_closed && is_last_segment; - 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); + match segments[i] { + kurbo::PathSeg::Line(line) => { + let start_point = line.p0; + let end_point = line.p1; + let handle = BezierHandles::Linear; + this.append_segment(start_point, end_point, handle, close_bezpath); + } + kurbo::PathSeg::Quad(quad_bez) => { + let start_point = quad_bez.p0; + let end_point = quad_bez.p2; + + let handle = BezierHandles::Quadratic { handle: point_to_dvec2(quad_bez.p1) }; + + this.append_segment(start_point, end_point, handle, close_bezpath); + } + kurbo::PathSeg::Cubic(cubic_bez) => { + let start_point = cubic_bez.p0; + let end_point = cubic_bez.p3; + + let handle = BezierHandles::Cubic { + handle_start: point_to_dvec2(cubic_bez.p1), + handle_end: point_to_dvec2(cubic_bez.p2), + }; + + this.append_segment(start_point, end_point, handle, close_bezpath); } - 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(); - // In case there is only one anchor point. - let first_segment_id = this.first_segment_id.unwrap_or(next_segment_id); - - this.vector_data.region_domain.push(next_region_id, first_segment_id..=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, - ), } } } From 0cc819c705c9fceee5510a990a62d75990353067 Mon Sep 17 00:00:00 2001 From: indierusty Date: Sun, 25 May 2025 12:21:28 +0530 Subject: [PATCH 3/5] refactor bounding box node --- node-graph/gcore/src/vector/vector_data.rs | 38 ++++++++++++--------- node-graph/gcore/src/vector/vector_nodes.rs | 11 ++++-- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index 1745c146a6..6a4d088630 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -12,6 +12,7 @@ use core::borrow::Borrow; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; pub use indexed::VectorDataIndex; +use kurbo::{Affine, Rect, Shape}; pub use modification::*; use std::collections::HashMap; @@ -193,29 +194,32 @@ impl VectorData { vector_data } + /// Compute the bounding boxes of the bezpaths without any transform + pub fn bounding_box_rect(&self) -> Option { + self.bounding_box_with_transform_rect(DAffine2::IDENTITY) + } + /// Compute the bounding boxes of the subpaths without any transform pub fn bounding_box(&self) -> Option<[DVec2; 2]> { - self.bounding_box_with_transform(DAffine2::IDENTITY) + self.bounding_box_with_transform_rect(DAffine2::IDENTITY) + .map(|rect| [DVec2::new(rect.x0, rect.y0), DVec2::new(rect.x1, rect.y1)]) } /// Compute the bounding boxes of the subpaths with the specified transform pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> { - let combine = |[a_min, a_max]: [DVec2; 2], [b_min, b_max]: [DVec2; 2]| [a_min.min(b_min), a_max.max(b_max)]; - - let anchor_bounds = self - .point_domain - .positions() - .iter() - .map(|&point| transform.transform_point2(point)) - .map(|point| [point, point]) - .reduce(combine); - - let segment_bounds = self - .segment_bezier_iter() - .map(|(_, bezier, _, _)| bezier.apply_transformation(|point| transform.transform_point2(point)).bounding_box()) - .reduce(combine); - - anchor_bounds.iter().chain(segment_bounds.iter()).copied().reduce(combine) + self.bounding_box_with_transform_rect(transform) + .map(|rect| [DVec2::new(rect.x0, rect.y0), DVec2::new(rect.x1, rect.y1)]) + } + + /// Compute the bounding boxes of the bezpaths with the specified transform + pub fn bounding_box_with_transform_rect(&self, transform: DAffine2) -> Option { + let combine = |r1: Rect, r2: Rect| r1.union(r2); + self.stroke_bezpath_iter() + .map(|mut bezpath| { + bezpath.apply_affine(Affine::new(transform.to_cols_array())); + bezpath.bounding_box() + }) + .reduce(combine) } /// Calculate the corners of the bounding box but with a nonzero size. diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index ab5225759e..d21ee6a5ab 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -16,7 +16,7 @@ use bezier_rs::{Join, ManipulatorGroup, Subpath, SubpathTValue}; use core::f64::consts::PI; use core::hash::{Hash, Hasher}; use glam::{DAffine2, DVec2}; -use kurbo::{Affine, BezPath, Shape}; +use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Shape}; use rand::{Rng, SeedableRng}; use std::collections::hash_map::DefaultHasher; @@ -1044,9 +1044,14 @@ async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTa let vector_data = vector_data.instance; let mut result = vector_data - .bounding_box() - .map(|bounding_box| VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1]))) + .bounding_box_rect() + .map(|bbox| { + let mut vector_data = VectorData::default(); + vector_data.append_bezpath(bbox.to_path(DEFAULT_ACCURACY)); + vector_data + }) .unwrap_or_default(); + result.style = vector_data.style.clone(); result.style.set_stroke_transform(DAffine2::IDENTITY); From e7bd88e9cedea616017fc1be5f1005eaaeb656e9 Mon Sep 17 00:00:00 2001 From: indierusty Date: Tue, 27 May 2025 10:40:31 +0530 Subject: [PATCH 4/5] fix append bezpath implementation. --- .../src/vector/vector_data/modification.rs | 111 ++++++++++++------ 1 file changed, 73 insertions(+), 38 deletions(-) diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index b18416b4b4..4a72ddffef 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -5,7 +5,7 @@ use crate::uuid::generate_uuid; use bezier_rs::BezierHandles; use core::hash::BuildHasher; use dyn_any::DynAny; -use kurbo::{BezPath, PathEl, PathSeg, Point}; +use kurbo::{BezPath, PathEl, Point}; use std::collections::{HashMap, HashSet}; /// Represents a procedural change to the [`PointDomain`] in [`VectorData`]. @@ -555,7 +555,8 @@ where } pub struct AppendBezpath<'a> { - first_point: Option, + first_point: Option, + last_point: Option, first_point_index: Option, last_point_index: Option, first_segment_id: Option, @@ -569,6 +570,7 @@ impl<'a> AppendBezpath<'a> { fn new(vector_data: &'a mut VectorData) -> Self { Self { first_point: None, + last_point: None, first_point_index: None, last_point_index: None, first_segment_id: None, @@ -579,72 +581,105 @@ impl<'a> AppendBezpath<'a> { } } - fn append_segment(&mut self, start_point: Point, end_point: Point, handle: BezierHandles, close_path: bool) { - if self.first_point.is_none() { - self.append_first_point(start_point); - } - - let next_point_index = if let (Some(first_point_index), true) = (self.first_point_index, close_path) { - first_point_index + fn append_segment_and_close_path(&mut self, point: Point, handle: BezierHandles) { + let handle = if self.first_point.unwrap() != point { + // If the first point is not same as the last point of the path then we append the segment + // with given handle and point and then close the path with linear handle. + self.append_segment(point, handle); + BezierHandles::Linear } 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(end_point)); - next_point_index + // if the endpoints are the same then we close the path with given handle. + handle }; + // Create a new segment. + let next_segment_id = self.segment_id.next_id(); + self.vector_data + .segment_domain + .push(next_segment_id, self.last_point_index.unwrap(), self.first_point_index.unwrap(), handle, StrokeId::ZERO); + + // Create a new region. + let next_region_id = self.vector_data.region_domain.next_id(); + let first_segment_id = self.first_segment_id.unwrap_or(next_segment_id); + let last_segment_id = next_segment_id; + + self.vector_data.region_domain.push(next_region_id, first_segment_id..=last_segment_id, FillId::ZERO); + } + + fn append_segment(&mut self, end_point: Point, handle: BezierHandles) { + // Append the point. + let next_point_index = self.vector_data.point_domain.ids().len(); + let next_point_id = self.point_id.next_id(); + + self.vector_data.point_domain.push(next_point_id, point_to_dvec2(end_point)); + + // Append the segment. 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); + .push(next_segment_id, self.last_point_index.unwrap(), next_point_index, handle, StrokeId::ZERO); + // Update the states. + self.last_point = Some(end_point); 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); } fn append_first_point(&mut self, point: Point) { self.first_point = Some(point); + self.last_point = Some(point); + + // Append the first point. 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)); + + // Update the state. self.first_point_index = Some(next_point_index); self.last_point_index = Some(next_point_index); } pub fn append_bezpath(vector_data: &'a mut VectorData, bezpath: BezPath) { let mut this = Self::new(vector_data); + let mut elements = bezpath.elements().iter().peekable(); - let is_closed = bezpath.elements().last().is_some_and(|elm| *elm == PathEl::ClosePath); - let segments = bezpath.segments().collect::>(); + while let Some(element) = elements.next() { + let close_path = elements.peek().is_some_and(|elm| **elm == PathEl::ClosePath); - for i in 0..segments.len() { - let is_last_segment = i + 1 == segments.len(); - let close_bezpath = is_closed && is_last_segment; - - match segments[i] { - kurbo::PathSeg::Line(line) => { - let start_point = line.p0; - let end_point = line.p1; + match *element { + PathEl::MoveTo(point) => this.append_first_point(point), + PathEl::LineTo(point) => { let handle = BezierHandles::Linear; - this.append_segment(start_point, end_point, handle, close_bezpath); + if close_path { + this.append_segment_and_close_path(point, handle); + } else { + this.append_segment(point, handle); + } } - kurbo::PathSeg::Quad(quad_bez) => { - let start_point = quad_bez.p0; - let end_point = quad_bez.p2; - - let handle = BezierHandles::Quadratic { handle: point_to_dvec2(quad_bez.p1) }; - - this.append_segment(start_point, end_point, handle, close_bezpath); + PathEl::QuadTo(point, point1) => { + let handle = BezierHandles::Quadratic { handle: point_to_dvec2(point) }; + if close_path { + this.append_segment_and_close_path(point1, handle); + } else { + this.append_segment(point1, handle); + } } - kurbo::PathSeg::Cubic(cubic_bez) => { - let start_point = cubic_bez.p0; - let end_point = cubic_bez.p3; - + PathEl::CurveTo(point, point1, point2) => { let handle = BezierHandles::Cubic { - handle_start: point_to_dvec2(cubic_bez.p1), - handle_end: point_to_dvec2(cubic_bez.p2), + handle_start: point_to_dvec2(point), + handle_end: point_to_dvec2(point1), }; - this.append_segment(start_point, end_point, handle, close_bezpath); + if close_path { + this.append_segment_and_close_path(point2, handle); + } else { + this.append_segment(point2, handle); + } + } + PathEl::ClosePath => { + // Already handled using `append_segment_and_close_path()`; + break; } } } From 96fd3f6da4c6c0e71ebd6d7ce647bee732a73ff1 Mon Sep 17 00:00:00 2001 From: indierusty Date: Tue, 27 May 2025 10:58:08 +0530 Subject: [PATCH 5/5] comments --- node-graph/gcore/src/vector/vector_data/modification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 4a72ddffef..4879cc9007 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -583,7 +583,7 @@ impl<'a> AppendBezpath<'a> { fn append_segment_and_close_path(&mut self, point: Point, handle: BezierHandles) { let handle = if self.first_point.unwrap() != point { - // If the first point is not same as the last point of the path then we append the segment + // If the first point is not the same as the last point of the path then we append the segment // with given handle and point and then close the path with linear handle. self.append_segment(point, handle); BezierHandles::Linear