From ed82c5f20fccd66a959334dee33351657968cdb6 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 3 Jan 2024 06:11:42 -0800 Subject: [PATCH] Copy to Points node: add Start/Stop Offset and Adaptive Spacing parameters --- .../document_node_types.rs | 5 ++- .../node_properties.rs | 10 +++++- node-graph/gcore/src/vector/vector_nodes.rs | 34 +++++++++++++++---- .../interpreted-executor/src/node_registry.rs | 2 +- 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 977c83288..42ed4b732 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -2656,10 +2656,13 @@ fn static_nodes() -> Vec { DocumentNodeDefinition { name: "Resample Points", category: "Vector", - implementation: NodeImplementation::proto("graphene_core::vector::ResamplePoints<_>"), + implementation: NodeImplementation::proto("graphene_core::vector::ResamplePoints<_, _, _, _>"), inputs: vec![ DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true), DocumentInputType::value("Spacing", TaggedValue::F64(100.), false), + DocumentInputType::value("Start Offset", TaggedValue::F64(0.), false), + DocumentInputType::value("Stop Offset", TaggedValue::F64(0.), false), + DocumentInputType::value("Adaptive Spacing", TaggedValue::Bool(false), false), ], outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], properties: node_properties::resample_points_properties, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index 1c2a5a855..f78f0e1a6 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -2044,8 +2044,16 @@ pub fn copy_to_points_properties(document_node: &DocumentNode, node_id: NodeId, pub fn resample_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { let spacing = number_widget(document_node, node_id, 1, "Spacing", NumberInput::default().min(1.), true); + let start_offset = number_widget(document_node, node_id, 2, "Start Offset", NumberInput::default().min(0.), true); + let stop_offset = number_widget(document_node, node_id, 3, "Stop Offset", NumberInput::default().min(0.), true); + let adaptive_spacing = bool_widget(document_node, node_id, 4, "Adaptive Spacing", true); - vec![LayoutGroup::Row { widgets: spacing }] + vec![ + LayoutGroup::Row { widgets: spacing }.with_tooltip("Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled)"), + LayoutGroup::Row { widgets: start_offset }.with_tooltip("Exclude some distance from the start of the path before the first instance"), + LayoutGroup::Row { widgets: stop_offset }.with_tooltip("Exclude some distance from the end of the path after the last instance"), + LayoutGroup::Row { widgets: adaptive_spacing }.with_tooltip("Round 'Spacing' to a nearby value that divides into the path length evenly"), + ] } /// Fill Node Widgets LayoutGroup diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index ade2d5cda..d133af051 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -204,12 +204,15 @@ async fn copy_to_points { +pub struct ResamplePoints { spacing: Spacing, + start_offset: StartOffset, + stop_offset: StopOffset, + adaptive_spacing: AdaptiveSpacing, } #[node_macro::node_fn(ResamplePoints)] -fn resample_points(mut vector_data: VectorData, spacing: f64) -> VectorData { +fn resample_points(mut vector_data: VectorData, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool) -> VectorData { for subpath in &mut vector_data.subpaths { if subpath.is_empty() || spacing.is_zero() || !spacing.is_finite() { continue; @@ -217,11 +220,30 @@ fn resample_points(mut vector_data: VectorData, spacing: f64) -> VectorData { subpath.apply_transform(vector_data.transform); let length = subpath.length(None); - let rounded_count = (length / spacing).round(); + let used_length = length - start_offset - stop_offset; + if used_length <= 0. { + continue; + } + let used_length_without_remainder = used_length - used_length % spacing; - if rounded_count >= 1. { - let new_anchors = (0..=rounded_count as usize).map(|c| subpath.evaluate(SubpathTValue::GlobalEuclidean(c as f64 / rounded_count))); - *subpath = Subpath::from_anchors(new_anchors, subpath.closed() && rounded_count as usize > 1); + let count = if adaptive_spacing { + (used_length / spacing).round() + } else { + (used_length / spacing + f64::EPSILON).floor() + }; + + if count >= 1. { + let new_anchors = (0..=count as usize).map(|c| { + let ratio = c as f64 / count; + + // With adaptive spacing, we widen or narrow the points (that's the rounding performed above) as necessary to ensure the last point is always at the end of the path + // Without adaptive spacing, we just evenly space the points at the exact specified spacing, usually falling short before the end of the path + + let used_length_here = if adaptive_spacing { used_length } else { used_length_without_remainder }; + let t = ratio * used_length_here + start_offset; + subpath.evaluate(SubpathTValue::GlobalEuclidean(t / length)) + }); + *subpath = Subpath::from_anchors(new_anchors, subpath.closed() && count as usize > 1); } subpath.apply_transform(vector_data.transform.inverse()); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 48f256311..7cfa61bd3 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -734,7 +734,7 @@ fn node_registry() -> HashMap, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, Footprint => VectorData]), async_node!(graphene_core::vector::CopyToPoints<_, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => VectorData, Footprint => GraphicGroup]), - register_node!(graphene_core::vector::ResamplePoints<_>, input: VectorData, params: [f64]), + register_node!(graphene_core::vector::ResamplePoints<_, _, _, _>, input: VectorData, params: [f64, f64, f64, bool]), register_node!(graphene_core::vector::SplinesFromPointsNode, input: VectorData, params: []), register_node!(graphene_core::vector::generator_nodes::CircleGenerator<_>, input: (), params: [f32]), register_node!(graphene_core::vector::generator_nodes::EllipseGenerator<_, _>, input: (), params: [f32, f32]),