From 11ba2cc0fe23cfd6ccbb59b771a13c7b5af32018 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 24 Jun 2025 04:55:27 -0700 Subject: [PATCH] New node: 'Split Segments' --- node-graph/gcore/src/ops.rs | 24 ------- node-graph/gcore/src/vector/vector_data.rs | 2 +- .../src/vector/vector_data/attributes.rs | 5 ++ node-graph/gcore/src/vector/vector_nodes.rs | 66 ++++++++++++++++++- 4 files changed, 70 insertions(+), 27 deletions(-) diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 191feda24..b5a06abfc 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -441,30 +441,6 @@ fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Option color } -// // Aims for interoperable compatibility with: -// // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map -// // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0) -// #[node_macro::node(category("Raster: Adjustment"))] -// async fn gradient_map>( -// _: impl Ctx, -// #[implementations( -// Color, -// RasterDataTable, -// GradientStops, -// )] -// mut image: T, -// gradient: GradientStops, -// reverse: bool, -// ) -> T { -// image.adjust(|color| { -// let intensity = color.luminance_srgb(); -// let intensity = if reverse { 1. - intensity } else { intensity }; -// gradient.evaluate(intensity as f64) -// }); - -// image -// } - /// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right). #[node_macro::node(category("Color"))] fn sample_gradient(_: impl Ctx, _primary: (), gradient: GradientStops, position: Fraction) -> Color { diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index 7d5a53635..66b2d22fc 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -412,7 +412,7 @@ impl VectorData { } pub fn other_colinear_handle(&self, handle: HandleId) -> Option { - let pair = self.colinear_manipulators.iter().find(|pair| pair.iter().any(|&val| val == handle))?; + let pair = self.colinear_manipulators.iter().find(|pair| pair.contains(&handle))?; let other = pair.iter().copied().find(|&val| val != handle)?; if handle.to_manipulator_point().get_anchor(self) == other.to_manipulator_point().get_anchor(self) { Some(other) diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index 4d4511cef..bb2ca1f91 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -132,6 +132,11 @@ impl PointDomain { self.position.push(position); } + pub fn push_unchecked(&mut self, id: PointId, position: DVec2) { + self.id.push(id); + self.position.push(position); + } + pub fn positions(&self) -> &[DVec2] { &self.position } diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 563cb5e12..179269ad6 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -1314,8 +1314,11 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, result_table } +/// Splits a path at a given progress from 0 to 1 along the path, creating two new subpaths from the original one (if the path is initially open) or one open subpath (if the path is initially closed). +/// +/// If multiple subpaths make up the path, the whole number part of the progress value selects the subpath and the decimal part determines the position along it. #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] -async fn split_path(_: impl Ctx, mut vector_data: VectorDataTable, t_value: f64, parameterized_distance: bool, reverse: bool) -> VectorDataTable { +async fn split_path(_: impl Ctx, mut vector_data: VectorDataTable, progress: Fraction, parameterized_distance: bool, reverse: bool) -> VectorDataTable { let euclidian = !parameterized_distance; let bezpaths = vector_data @@ -1325,7 +1328,7 @@ async fn split_path(_: impl Ctx, mut vector_data: VectorDataTable, t_value: f64, .collect::>(); let bezpath_count = bezpaths.len() as f64; - let t_value = t_value.clamp(0., bezpath_count); + let t_value = progress.clamp(0., bezpath_count); let t_value = if reverse { bezpath_count - t_value } else { t_value }; let index = if t_value >= bezpath_count { (bezpath_count - 1.) as usize } else { t_value as usize }; @@ -1353,7 +1356,65 @@ async fn split_path(_: impl Ctx, mut vector_data: VectorDataTable, t_value: f64, vector_data } +/// Splits path segments into separate disconnected pieces where each is a distinct subpath. +#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] +async fn split_segments(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable { + // Iterate through every segment and make a copy of each of its endpoints, then reassign each segment's endpoints to its own unique point copy + for vector_data_instance in vector_data.instance_mut_iter() { + let points_count = vector_data_instance.instance.point_domain.ids().len(); + let segments_count = vector_data_instance.instance.segment_domain.ids().len(); + + let mut point_usages = vec![0_usize; points_count]; + + // Count how many times each point is used as an endpoint of the segments + let start_points = vector_data_instance.instance.segment_domain.start_point().iter(); + let end_points = vector_data_instance.instance.segment_domain.end_point().iter(); + for (&start, &end) in start_points.zip(end_points) { + point_usages[start] += 1; + point_usages[end] += 1; + } + + let mut new_points = PointDomain::new(); + let mut offset_sum: usize = 0; + let mut points_with_new_offsets = Vec::with_capacity(points_count); + + // Build a new point domain with the original points, but with duplications based on their extra usages by the segments + for (index, (point_id, point)) in vector_data_instance.instance.point_domain.iter().enumerate() { + // Ensure at least one usage to preserve free-floating points not connected to any segments + let usage_count = point_usages[index].max(1); + + new_points.push_unchecked(point_id, point); + + for i in 1..usage_count { + new_points.push_unchecked(point_id.generate_from_hash(i as u64), point); + } + + points_with_new_offsets.push(offset_sum); + offset_sum += usage_count; + } + + // Reconcile the segment domain with the new points + vector_data_instance.instance.point_domain = new_points; + for original_segment_index in 0..segments_count { + let original_point_start_index = vector_data_instance.instance.segment_domain.start_point()[original_segment_index]; + let original_point_end_index = vector_data_instance.instance.segment_domain.end_point()[original_segment_index]; + + point_usages[original_point_start_index] -= 1; + point_usages[original_point_end_index] -= 1; + + let start_usage = points_with_new_offsets[original_point_start_index] + point_usages[original_point_start_index]; + let end_usage = points_with_new_offsets[original_point_end_index] + point_usages[original_point_end_index]; + + vector_data_instance.instance.segment_domain.set_start_point(original_segment_index, start_usage); + vector_data_instance.instance.segment_domain.set_end_point(original_segment_index, end_usage); + } + } + + vector_data +} + /// Determines the position of a point on the path, given by its progress from 0 to 1 along the path. +/// /// If multiple subpaths make up the path, the whole number part of the progress value selects the subpath and the decimal part determines the position along it. #[node_macro::node(name("Position on Path"), category("Vector: Measure"), path(graphene_core::vector))] async fn position_on_path( @@ -1390,6 +1451,7 @@ async fn position_on_path( } /// Determines the angle of the tangent at a point on the path, given by its progress from 0 to 1 along the path. +/// /// If multiple subpaths make up the path, the whole number part of the progress value selects the subpath and the decimal part determines the position along it. #[node_macro::node(name("Tangent on Path"), category("Vector: Measure"), path(graphene_core::vector))] async fn tangent_on_path(