-
-
Notifications
You must be signed in to change notification settings - Fork 811
Add new node "Decimate" #2823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add new node "Decimate" #2823
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1993,6 +1993,103 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> { | |
description: Cow::Borrowed("Convert vector geometry into a polyline composed of evenly spaced points."), | ||
properties: Some("sample_polyline_properties"), | ||
}, | ||
DocumentNodeDefinition { | ||
identifier: "Decimate", | ||
category: "Vector: Modifier", | ||
node_template: NodeTemplate { | ||
document_node: DocumentNode { | ||
implementation: DocumentNodeImplementation::Network(NodeNetwork { | ||
exports: vec![NodeInput::node(NodeId(2), 0)], | ||
nodes: [ | ||
DocumentNode { | ||
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)], | ||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode")), | ||
manual_composition: Some(generic!(T)), | ||
skip_deduplication: true, | ||
..Default::default() | ||
}, | ||
Comment on lines
+2004
to
+2010
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is there a monitor node? |
||
DocumentNode { | ||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(f64), 1)], | ||
manual_composition: Some(generic!(T)), | ||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::DecimateNode")), | ||
..Default::default() | ||
}, | ||
DocumentNode { | ||
inputs: vec![NodeInput::node(NodeId(1), 0)], | ||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), | ||
manual_composition: Some(generic!(T)), | ||
..Default::default() | ||
}, | ||
Comment on lines
+2017
to
+2022
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is there a memo node? |
||
] | ||
.into_iter() | ||
.enumerate() | ||
.map(|(id, node)| (NodeId(id as u64), node)) | ||
.collect(), | ||
..Default::default() | ||
}), | ||
inputs: vec![ | ||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true), | ||
NodeInput::value(TaggedValue::F64(1.0), false), // Tolerance | ||
], | ||
..Default::default() | ||
}, | ||
persistent_node_metadata: DocumentNodePersistentMetadata { | ||
network_metadata: Some(NodeNetworkMetadata { | ||
persistent_metadata: NodeNetworkPersistentMetadata { | ||
node_metadata: [ | ||
DocumentNodeMetadata { | ||
persistent_metadata: DocumentNodePersistentMetadata { | ||
display_name: "Monitor".to_string(), | ||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), | ||
..Default::default() | ||
}, | ||
..Default::default() | ||
}, | ||
DocumentNodeMetadata { | ||
persistent_metadata: DocumentNodePersistentMetadata { | ||
display_name: "Decimate".to_string(), | ||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), | ||
..Default::default() | ||
}, | ||
..Default::default() | ||
}, | ||
DocumentNodeMetadata { | ||
persistent_metadata: DocumentNodePersistentMetadata { | ||
display_name: "Memoize".to_string(), | ||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), | ||
..Default::default() | ||
}, | ||
..Default::default() | ||
}, | ||
] | ||
.into_iter() | ||
.enumerate() | ||
.map(|(id, node)| (NodeId(id as u64), node)) | ||
.collect(), | ||
..Default::default() | ||
}, | ||
..Default::default() | ||
}), | ||
input_properties: vec![ | ||
("Vector Data", "The polyline to be simplified.").into(), | ||
PropertiesRow::with_override( | ||
"Tolerance", | ||
"Maximum allowed deviation from the original polyline.", | ||
WidgetOverride::Number(NumberInputSettings { | ||
min: Some(0.0), | ||
step: Some(0.1), | ||
unit: Some(" px".to_string()), | ||
..Default::default() | ||
}), | ||
), | ||
], | ||
output_names: vec!["Vector".to_string()], | ||
..Default::default() | ||
}, | ||
}, | ||
description: Cow::Borrowed("Simplifies a polyline using the Ramer–Douglas–Peucker algorithm."), | ||
properties: None, | ||
}, | ||
DocumentNodeDefinition { | ||
identifier: "Scatter Points", | ||
category: "Vector: Modifier", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1138,6 +1138,97 @@ where | |
output_table | ||
} | ||
|
||
/// Simplifies a polyline using the Ramer–Douglas–Peucker algorithm. | ||
#[node_macro::node(category("Vector: Modifier"), name("Decimate"), path(graphene_core::vector))] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need to specify the name here. |
||
async fn decimate( | ||
_: impl Ctx, | ||
vector_data: VectorDataTable, | ||
#[default(1.0)] | ||
#[hard_min(0.0)] | ||
tolerance: f64, | ||
) -> VectorDataTable { | ||
let mut result_table = VectorDataTable::default(); | ||
|
||
for vector_data_instance in vector_data.instance_iter() { | ||
let mut result = VectorData { | ||
style: vector_data_instance.instance.style.clone(), | ||
..Default::default() | ||
}; | ||
|
||
for subpath in vector_data_instance.instance.stroke_bezier_paths() { | ||
let points: Vec<DVec2> = subpath.manipulator_groups().iter().map(|g| g.anchor).collect(); | ||
let closed = subpath.closed(); | ||
|
||
let simplified = if points.len() > 2 { douglas_peucker(&points, tolerance, closed) } else { points }; | ||
|
||
// Rebuild the subpath from the simplified points | ||
let mut new_groups = Vec::new(); | ||
let mut id_gen = PointId::generate(); | ||
for pt in simplified { | ||
new_groups.push(bezier_rs::ManipulatorGroup { | ||
anchor: pt, | ||
in_handle: None, | ||
out_handle: None, | ||
id: id_gen, | ||
}); | ||
id_gen = id_gen.next_id(); | ||
} | ||
let new_subpath = Subpath::new(new_groups, closed); | ||
result.append_subpath(new_subpath, closed); | ||
} | ||
|
||
result_table.push(Instance { | ||
instance: result, | ||
transform: vector_data_instance.transform, | ||
alpha_blending: vector_data_instance.alpha_blending, | ||
source_node_id: vector_data_instance.source_node_id, | ||
}); | ||
} | ||
|
||
result_table | ||
} | ||
|
||
fn douglas_peucker(points: &[DVec2], epsilon: f64, closed: bool) -> Vec<DVec2> { | ||
if points.len() < 2 { | ||
return points.to_vec(); | ||
} | ||
|
||
let (_first, _lastt) = (0, points.len() - 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't prefix local variables with an underscore. That usually signifies that they are unused. |
||
|
||
// For closed paths, treat as open for simplification, then close at the end | ||
let (_first, _last, _is_closed) = if closed && points.len() > 2 { (0, points.len() - 1, true) } else { (0, points.len() - 1, false) }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line could be simplified to let is_closed = closed && points.len() > 2; Since the other variables don't change and were already defined. |
||
|
||
let mut dmax = 0.0; | ||
let mut index = 0; | ||
|
||
for i in (_first + 1).._last { | ||
let d = perpendicular_distance(points[i], points[_first], points[_last]); | ||
if d > dmax { | ||
index = i; | ||
dmax = d; | ||
} | ||
} | ||
|
||
if dmax > epsilon { | ||
let mut rec_results1 = douglas_peucker(&points[_first..=index], epsilon, false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since first is always zero, this could just be |
||
let mut rec_results2 = douglas_peucker(&points[index..=_last], epsilon, false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since |
||
|
||
// Remove the last point of the first list to avoid duplication | ||
rec_results1.pop(); | ||
rec_results1.append(&mut rec_results2); | ||
rec_results1 | ||
} else { | ||
vec![points[_first], points[_last]] | ||
} | ||
} | ||
|
||
/// Calculates the perpendicular distance from a point to a line defined by two points. | ||
fn perpendicular_distance(point: DVec2, line_start: DVec2, line_end: DVec2) -> f64 { | ||
let num = ((line_end.y - line_start.y) * point.x - (line_end.x - line_start.x) * point.y + line_end.x * line_start.y - line_end.y * line_start.x).abs(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can be simplified with |
||
let den = (line_end - line_start).length(); | ||
if den.abs() < 1e-10 { (point - line_start).length() } else { num / den } | ||
} | ||
|
||
/// Convert vector geometry into a polyline composed of evenly spaced points. | ||
#[node_macro::node(category(""), path(graphene_core::vector))] | ||
async fn sample_polyline( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't usually need to manually define the document node definition as it can be automagically generated.