Add new node "Decimate"

This commit is contained in:
Mostafa Attia 2025-07-03 07:06:15 +01:00
parent a182a7347e
commit c48c704ebe
2 changed files with 188 additions and 0 deletions

View file

@ -2005,6 +2005,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()
},
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()
},
]
.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 RamerDouglasPeucker algorithm."),
properties: None,
},
DocumentNodeDefinition {
identifier: "Scatter Points",
category: "Vector: Modifier",

View file

@ -1138,6 +1138,97 @@ where
output_table
}
/// Simplifies a polyline using the RamerDouglasPeucker algorithm.
#[node_macro::node(category("Vector: Modifier"), name("Decimate"), path(graphene_core::vector))]
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);
// 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) };
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);
let mut rec_results2 = douglas_peucker(&points[index..=_last], epsilon, false);
// 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();
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(