Skip to content

Commit 504af4e

Browse files
Moharum1Keavonindierustyseam0s-dev4adex
authored
Rename 'Sample Points' node to 'Sample Polyline' and add a parameter spacing based on separation or quantity (#2727)
* Added Count point Radio button to property pannel * Implemented on Count radio button functionality * Fixed linting and Title case problem * Fixing more linting problem * Instance tables refactor part 8: Make repeater nodes use pivot not bbox and output instance type not group; rename 'Flatten Vector Elements' to 'Flatten Path' and add 'Flatten Vector' (#2697) Make repeater nodes use pivot not bbox and output instance type not group; rename 'Flatten Vector Elements' to 'Flatten Path' and add 'Flatten Vector' * Refactor the 'Bounding Box' node to use Kurbo instead of Bezier-rs (#2662) * use kurbo's default accuracy constant * fix append_bezpath() method * refactor bounding box node * fix append bezpath implementation. * comments --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> * Add overlays for free-floating anchors on hovered/selected vector layers (#2630) * Add selection overlay for free-floating anchors * Add hover overlay for free-floating anchors * Refactor outline_free_floating anchor * Add single-anchor click targets on VectorData * Modify ClickTarget to adapt for Subpath and PointGroup * Fix Rust formatting * Remove debug statements * Add point groups support in VectorDataTable::add_upstream_click_targets * Improve overlay for free floating anchors * Remove datatype for nodes_to_shift * Fix formatting in select_tool.rs * Lints * Code review * Remove references to point_group * Refactor ManipulatorGroup for FreePoint in ClickTargetGroup * Rename ClickTargetGroup to ClickTargetType * Refactor outline_free_floating_anchors into outline * Adapt TransformCage to disable dragging and rotating on a single anchor layer * Fix hover on single points * Fix comments * Lints * Code review pass --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> * Add anchor sliding along adjacent segments in the Path tool (#2682) * Improved comments * Add point sliding with approximate t value * Add similarity calculation * Numerical approach to fit the curve * Reliable point sliding for cubic segments * Fix formatting and clean comments * Fix cubic with one handle logic * Cancel on right click and escape * Two parameter optimization * Esc/ Right click cancellation * Code review * Fix dynamic hints * Revert selected_points_counts and fix comments * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> * Fix Sample Points node to avoid duplicating endpoints instead of closing its sampled paths (#2714) * Skip duplicate endpoint and close sampled paths in Sample Points node Closes #2713 * Comment --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> * Implemented on Count radio button functionality * Fixed linting and Title case problem * The sample count can now work with adaptive spacing * Readying for production * Rename to 'Sample Polyline' and add migration * Upgrade demo artwork * Add monomorphization --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: Priyanshu <indierusty@gmail.com> Co-authored-by: seam0s <153828136+seam0s-dev@users.noreply.github.com> Co-authored-by: Adesh Gupta <148623820+4adex@users.noreply.github.com> Co-authored-by: Ezbaze <68749104+Ezbaze@users.noreply.github.com>
1 parent 4a65ad2 commit 504af4e

File tree

12 files changed

+206
-63
lines changed

12 files changed

+206
-63
lines changed

demo-artwork/parametric-dunescape.graphite

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo-artwork/procedural-string-lights.graphite

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo-artwork/red-dress.graphite

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,12 +1822,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
18221822
properties: None,
18231823
},
18241824
DocumentNodeDefinition {
1825-
identifier: "Sample Points",
1825+
identifier: "Sample Polyline",
18261826
category: "Vector: Modifier",
18271827
node_template: NodeTemplate {
18281828
document_node: DocumentNode {
18291829
implementation: DocumentNodeImplementation::Network(NodeNetwork {
1830-
exports: vec![NodeInput::node(NodeId(4), 0)], // Taken from output 0 of Sample Points
1830+
exports: vec![NodeInput::node(NodeId(4), 0)],
18311831
nodes: [
18321832
DocumentNode {
18331833
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
@@ -1838,13 +1838,15 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
18381838
DocumentNode {
18391839
inputs: vec![
18401840
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
1841-
NodeInput::network(concrete!(f64), 1), // From the document node's parameters
1842-
NodeInput::network(concrete!(f64), 2), // From the document node's parameters
1843-
NodeInput::network(concrete!(f64), 3), // From the document node's parameters
1844-
NodeInput::network(concrete!(bool), 4), // From the document node's parameters
1845-
NodeInput::node(NodeId(0), 0), // From output 0 of SubpathSegmentLengthsNode
1841+
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
1842+
NodeInput::network(concrete!(f64), 2),
1843+
NodeInput::network(concrete!(f64), 3),
1844+
NodeInput::network(concrete!(f64), 4),
1845+
NodeInput::network(concrete!(f64), 5),
1846+
NodeInput::network(concrete!(bool), 6),
1847+
NodeInput::node(NodeId(0), 0),
18461848
],
1847-
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePointsNode")),
1849+
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePolylineNode")),
18481850
manual_composition: Some(generic!(T)),
18491851
..Default::default()
18501852
},
@@ -1875,6 +1877,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
18751877
}),
18761878
inputs: vec![
18771879
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
1880+
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
1881+
NodeInput::value(TaggedValue::F64(100.), false),
18781882
NodeInput::value(TaggedValue::F64(100.), false),
18791883
NodeInput::value(TaggedValue::F64(0.), false),
18801884
NodeInput::value(TaggedValue::F64(0.), false),
@@ -1889,14 +1893,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
18891893
DocumentNodeMetadata {
18901894
persistent_metadata: DocumentNodePersistentMetadata {
18911895
display_name: "Subpath Segment Lengths".to_string(),
1892-
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 5)),
1896+
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 7)),
18931897
..Default::default()
18941898
},
18951899
..Default::default()
18961900
},
18971901
DocumentNodeMetadata {
18981902
persistent_metadata: DocumentNodePersistentMetadata {
1899-
display_name: "Sample Points".to_string(),
1903+
display_name: "Sample Polyline".to_string(),
19001904
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
19011905
..Default::default()
19021906
},
@@ -1937,18 +1941,28 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
19371941
}),
19381942
input_properties: vec![
19391943
("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
1944+
Into::<PropertiesRow>::into(("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING)),
19401945
PropertiesRow::with_override(
1941-
"Spacing",
1942-
"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).",
1946+
"Separation",
1947+
node_properties::SAMPLE_POLYLINE_TOOLTIP_SEPARATION,
19431948
WidgetOverride::Number(NumberInputSettings {
1944-
min: Some(1.),
1949+
min: Some(0.),
19451950
unit: Some(" px".to_string()),
19461951
..Default::default()
19471952
}),
19481953
),
1954+
PropertiesRow::with_override(
1955+
"Quantity",
1956+
node_properties::SAMPLE_POLYLINE_TOOLTIP_QUANTITY,
1957+
WidgetOverride::Number(NumberInputSettings {
1958+
min: Some(2.),
1959+
is_integer: true,
1960+
..Default::default()
1961+
}),
1962+
),
19491963
PropertiesRow::with_override(
19501964
"Start Offset",
1951-
"Exclude some distance from the start of the path before the first instance.",
1965+
node_properties::SAMPLE_POLYLINE_TOOLTIP_START_OFFSET,
19521966
WidgetOverride::Number(NumberInputSettings {
19531967
min: Some(0.),
19541968
unit: Some(" px".to_string()),
@@ -1957,21 +1971,21 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
19571971
),
19581972
PropertiesRow::with_override(
19591973
"Stop Offset",
1960-
"Exclude some distance from the end of the path after the last instance.",
1974+
node_properties::SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET,
19611975
WidgetOverride::Number(NumberInputSettings {
19621976
min: Some(0.),
19631977
unit: Some(" px".to_string()),
19641978
..Default::default()
19651979
}),
19661980
),
1967-
Into::<PropertiesRow>::into(("Adaptive Spacing", "Round 'Spacing' to a nearby value that divides into the path length evenly.")),
1981+
Into::<PropertiesRow>::into(("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING)),
19681982
],
19691983
output_names: vec!["Vector".to_string()],
19701984
..Default::default()
19711985
},
19721986
},
19731987
description: Cow::Borrowed("Convert vector geometry into a polyline composed of evenly spaced points."),
1974-
properties: None,
1988+
properties: Some("sample_polyline_properties"),
19751989
},
19761990
DocumentNodeDefinition {
19771991
identifier: "Scatter Points",
@@ -2344,6 +2358,7 @@ fn static_node_properties() -> NodeProperties {
23442358
map.insert("math_properties".to_string(), Box::new(node_properties::math_properties));
23452359
map.insert("rectangle_properties".to_string(), Box::new(node_properties::rectangle_properties));
23462360
map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties));
2361+
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
23472362
map.insert(
23482363
"identity_properties".to_string(),
23492364
Box::new(|_node_id, _context| node_properties::string_properties("The identity node simply passes its data through.")),

editor/src/messages/portfolio/document/node_graph/node_properties.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
2222
use graphene_std::text::Font;
2323
use graphene_std::transform::{Footprint, ReferencePoint};
2424
use graphene_std::vector::VectorDataTable;
25-
use graphene_std::vector::misc::CentroidType;
2625
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
2726
use graphene_std::vector::misc::{BooleanOperation, GridType};
27+
use graphene_std::vector::misc::{CentroidType, PointSpacingType};
2828
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
2929
use graphene_std::vector::style::{GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
3030
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
@@ -238,6 +238,7 @@ pub(crate) fn property_from_type(
238238
Some(x) if x == TypeId::of::<PaintOrder>() => enum_choice::<PaintOrder>().for_socket(default_info).property_row(),
239239
Some(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),
240240
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
241+
Some(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
241242
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
242243
Some(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
243244
Some(x) if x == TypeId::of::<LuminanceCalculation>() => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),
@@ -1225,6 +1226,64 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
12251226
widgets
12261227
}
12271228

1229+
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SPACING: &str = "Use a point sampling density controlled by a distance between, or specific number of, points.";
1230+
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SEPARATION: &str = "Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).";
1231+
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_QUANTITY: &str = "Number of points to place along the path.";
1232+
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_START_OFFSET: &str = "Exclude some distance from the start of the path before the first instance.";
1233+
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET: &str = "Exclude some distance from the end of the path after the last instance.";
1234+
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING: &str = "Round 'Separation' to a nearby value that divides into the path length evenly.";
1235+
1236+
pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
1237+
use graphene_std::vector::sample_polyline::*;
1238+
1239+
let document_node = match get_document_node(node_id, context) {
1240+
Ok(document_node) => document_node,
1241+
Err(err) => {
1242+
log::error!("Could not get document node in sample_polyline_properties: {err}");
1243+
return Vec::new();
1244+
}
1245+
};
1246+
1247+
let current_spacing = document_node.inputs.get(SpacingInput::INDEX).and_then(|input| input.as_value()).cloned();
1248+
let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)));
1249+
1250+
let spacing = enum_choice::<PointSpacingType>()
1251+
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::INDEX, true, context))
1252+
.property_row();
1253+
let separation = number_widget(
1254+
ParameterWidgetsInfo::from_index(document_node, node_id, SeparationInput::INDEX, true, context),
1255+
NumberInput::default().min(0.).unit(" px"),
1256+
);
1257+
let quantity = number_widget(
1258+
ParameterWidgetsInfo::from_index(document_node, node_id, QuantityInput::INDEX, true, context),
1259+
NumberInput::default().min(2.).int(),
1260+
);
1261+
let start_offset = number_widget(
1262+
ParameterWidgetsInfo::from_index(document_node, node_id, StartOffsetInput::INDEX, true, context),
1263+
NumberInput::default().min(0.).unit(" px"),
1264+
);
1265+
let stop_offset = number_widget(
1266+
ParameterWidgetsInfo::from_index(document_node, node_id, StopOffsetInput::INDEX, true, context),
1267+
NumberInput::default().min(0.).unit(" px"),
1268+
);
1269+
let adaptive_spacing = bool_widget(
1270+
ParameterWidgetsInfo::from_index(document_node, node_id, AdaptiveSpacingInput::INDEX, true, context),
1271+
CheckboxInput::default().disabled(is_quantity),
1272+
);
1273+
1274+
vec![
1275+
spacing.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_SPACING),
1276+
match current_spacing {
1277+
Some(TaggedValue::PointSpacingType(PointSpacingType::Separation)) => LayoutGroup::Row { widgets: separation }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_SEPARATION),
1278+
Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)) => LayoutGroup::Row { widgets: quantity }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_QUANTITY),
1279+
_ => LayoutGroup::Row { widgets: vec![] },
1280+
},
1281+
LayoutGroup::Row { widgets: start_offset }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_START_OFFSET),
1282+
LayoutGroup::Row { widgets: stop_offset }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET),
1283+
LayoutGroup::Row { widgets: adaptive_spacing }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING),
1284+
]
1285+
}
1286+
12281287
pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
12291288
use graphene_std::raster::exposure::*;
12301289

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
441441
let document_name = document_name.replace("__DO_NOT_UPGRADE__", "");
442442

443443
const TEXT_REPLACEMENTS: [(&str, &str); 2] = [
444-
("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePointsNode"),
444+
("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePolylineNode"),
445445
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
446446
];
447447
let document_serialized_content = TEXT_REPLACEMENTS
@@ -1074,6 +1074,31 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
10741074

10751075
document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string());
10761076
}
1077+
1078+
if reference == "Sample Points" && inputs_count == 5 {
1079+
// TODO: Rename to "Sample Polyline", also remove segment generation from "Scatter Points"
1080+
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
1081+
let new_node_template = node_definition.default_node_template();
1082+
let document_node = new_node_template.document_node;
1083+
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
1084+
document
1085+
.network_interface
1086+
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
1087+
1088+
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
1089+
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
1090+
1091+
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
1092+
document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);
1093+
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
1094+
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[1].clone(), network_path);
1095+
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
1096+
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
1097+
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
1098+
1099+
// TODO: Rename to "Sample Polyline", also remove segment generation from "Scatter Points"
1100+
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
1101+
}
10771102
}
10781103

10791104
// TODO: Eventually remove this document upgrade code

node-graph/gcore/src/logic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::vector::VectorDataTable;
22
use crate::{Color, Context, Ctx};
33
use glam::{DAffine2, DVec2};
44

5-
#[node_macro::node(category("Debug"))]
5+
#[node_macro::node(category("Debug"), name("Log to Console"))]
66
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
77
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
88
log::debug!("{:#?}", value);

node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::poisson_disk::poisson_disk_sample;
2-
use crate::vector::misc::dvec2_to_point;
2+
use crate::vector::misc::{PointSpacingType, dvec2_to_point};
33
use glam::DVec2;
44
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, Rect, Shape};
55

@@ -67,7 +67,15 @@ pub fn tangent_on_bezpath(bezpath: &BezPath, t: f64, euclidian: bool, segments_l
6767
}
6868
}
6969

70-
pub fn sample_points_on_bezpath(bezpath: BezPath, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, segments_length: &[f64]) -> Option<BezPath> {
70+
pub fn sample_polyline_on_bezpath(
71+
bezpath: BezPath,
72+
point_spacing_type: PointSpacingType,
73+
amount: f64,
74+
start_offset: f64,
75+
stop_offset: f64,
76+
adaptive_spacing: bool,
77+
segments_length: &[f64],
78+
) -> Option<BezPath> {
7179
let mut sample_bezpath = BezPath::new();
7280

7381
let was_closed = matches!(bezpath.elements().last(), Some(PathEl::ClosePath));
@@ -78,22 +86,33 @@ pub fn sample_points_on_bezpath(bezpath: BezPath, spacing: f64, start_offset: f6
7886
// Adjust the usable length by subtracting start and stop offsets.
7987
let mut used_length = total_length - start_offset - stop_offset;
8088

89+
// Sanity check that the usable length is positive.
8190
if used_length <= 0. {
8291
return None;
8392
}
8493

94+
const SAFETY_MAX_COUNT: f64 = 10_000. - 1.;
95+
8596
// Determine the number of points to generate along the path.
86-
let sample_count = if adaptive_spacing {
87-
// Calculate point count to evenly distribute points while covering the entire path.
88-
// With adaptive spacing, we widen or narrow the points as necessary to ensure the last point is always at the end of the path.
89-
(used_length / spacing).round()
90-
} else {
91-
// Calculate point count based on exact spacing, which may not cover the entire path.
92-
93-
// Without adaptive spacing, we just evenly space the points at the exact specified spacing, usually falling short before the end of the path.
94-
let count = (used_length / spacing + f64::EPSILON).floor();
95-
used_length -= used_length % spacing;
96-
count
97+
let sample_count = match point_spacing_type {
98+
PointSpacingType::Separation => {
99+
let spacing = amount.min(used_length - f64::EPSILON);
100+
101+
if adaptive_spacing {
102+
// Calculate point count to evenly distribute points while covering the entire path.
103+
// With adaptive spacing, we widen or narrow the points as necessary to ensure the last point is always at the end of the path.
104+
(used_length / spacing).round().min(SAFETY_MAX_COUNT)
105+
} else {
106+
// Calculate point count based on exact spacing, which may not cover the entire path.
107+
// Without adaptive spacing, we just evenly space the points at the exact specified spacing, usually falling short before the end of the path.
108+
let count = (used_length / spacing + f64::EPSILON).floor().min(SAFETY_MAX_COUNT);
109+
if count != SAFETY_MAX_COUNT {
110+
used_length -= used_length % spacing;
111+
}
112+
count
113+
}
114+
}
115+
PointSpacingType::Quantity => (amount - 1.).floor().clamp(1., SAFETY_MAX_COUNT),
97116
};
98117

99118
// Skip if there are no points to generate.

node-graph/gcore/src/vector/misc.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ pub enum MergeByDistanceAlgorithm {
103103
Topological,
104104
}
105105

106+
#[repr(C)]
107+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
108+
#[widget(Radio)]
109+
pub enum PointSpacingType {
110+
#[default]
111+
/// The desired spacing distance between points.
112+
Separation,
113+
/// The exact number of points to span the path.
114+
Quantity,
115+
}
116+
106117
pub fn point_to_dvec2(point: Point) -> DVec2 {
107118
DVec2 { x: point.x, y: point.y }
108119
}

0 commit comments

Comments
 (0)