Skip to content

Implement new nodes 'Path Length', 'Count' and 'Split Path'. #2731

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,55 @@ use crate::vector::misc::dvec2_to_point;
use glam::DVec2;
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, Rect, Shape};

/// Splits the `bezpath` at `t` value which lie in the range of [0, 1].
/// Returns None if the `bezpath` given has zero segments or t is very close to 0 or 1.
pub fn split_bezpath(bezpath: &BezPath, t: f64, euclidian: bool) -> Option<(BezPath, BezPath)> {
if t <= f64::EPSILON || (1. - t) <= f64::EPSILON || bezpath.segments().count() == 0 {
return None;
}

// Get the segment which lie at the split.
let (segment_index, t) = t_value_to_parametric(bezpath, t, euclidian, None);
let segment = bezpath.get_seg(segment_index + 1).unwrap();

// Divide the segment.
let first_segment = segment.subsegment(0f64..t);
let second_segment = segment.subsegment(t..1.);

let mut first_bezpath = BezPath::new();
let mut second_bezpath = BezPath::new();

// Append the segments upto the subdividing segment from original bezpath to first bezpath.
for segment in bezpath.segments().take(segment_index) {
if first_bezpath.elements().is_empty() {
first_bezpath.move_to(segment.start());
}
first_bezpath.push(segment.as_path_el());
}

// Append the first segment of the subdivided segment.
if first_bezpath.elements().is_empty() {
first_bezpath.move_to(first_segment.start());
}
first_bezpath.push(first_segment.as_path_el());

// Append the second segment of the subdivided segment in the second bezpath.
if second_bezpath.elements().is_empty() {
second_bezpath.move_to(second_segment.start());
}
second_bezpath.push(second_segment.as_path_el());

// Append the segments after the subdividing segment from original bezpath to second bezpath.
for segment in bezpath.segments().skip(segment_index + 1) {
if second_bezpath.elements().is_empty() {
second_bezpath.move_to(segment.start());
}
second_bezpath.push(segment.as_path_el());
}

Some((first_bezpath, second_bezpath))
}

pub fn position_on_bezpath(bezpath: &BezPath, t: f64, euclidian: bool, segments_length: Option<&[f64]>) -> Point {
let (segment_index, t) = t_value_to_parametric(bezpath, t, euclidian, segments_length);
bezpath.get_seg(segment_index + 1).unwrap().eval(t)
Expand Down
94 changes: 92 additions & 2 deletions node-graph/gcore/src/vector/vector_nodes.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::algorithms::bezpath_algorithms::{self, position_on_bezpath, sample_points_on_bezpath, tangent_on_bezpath};
use super::algorithms::bezpath_algorithms::{self, position_on_bezpath, sample_points_on_bezpath, split_bezpath, tangent_on_bezpath};
use super::algorithms::offset_subpath::offset_subpath;
use super::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
use super::misc::{CentroidType, point_to_dvec2};
Expand Down Expand Up @@ -1295,6 +1295,43 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
result_table
}

#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn split_path(_: impl Ctx, mut vector_data: VectorDataTable, t_value: f64, parameterized_distance: bool, reverse: bool) -> VectorDataTable {
let euclidian = !parameterized_distance;

let bezpaths = vector_data
.instance_ref_iter()
.enumerate()
.flat_map(|(instance_row_index, vector_data)| vector_data.instance.stroke_bezpath_iter().map(|bezpath| (instance_row_index, bezpath)).collect::<Vec<_>>())
.collect::<Vec<_>>();

let bezpath_count = bezpaths.len() as f64;
let t_value = t_value.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 };

if let Some((instance_row_index, bezpath)) = bezpaths.get(index).cloned() {
let mut result_vector_data = VectorData::default();
result_vector_data.style = vector_data.get(instance_row_index).unwrap().instance.style.clone();

for (_, (_, bezpath)) in bezpaths.iter().enumerate().filter(|(i, (ri, _))| *i != index && *ri == instance_row_index) {
result_vector_data.append_bezpath(bezpath.clone());
}
let t = if t_value == bezpath_count { 1. } else { t_value.fract() };

if let Some((first, second)) = split_bezpath(&bezpath, t, euclidian) {
result_vector_data.append_bezpath(first);
result_vector_data.append_bezpath(second);
} else {
result_vector_data.append_bezpath(bezpath);
}

*vector_data.get_mut(instance_row_index).unwrap().instance = result_vector_data;
}

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"), path(graphene_core::vector))]
Expand Down Expand Up @@ -1863,6 +1900,29 @@ fn merge_by_distance(_: impl Ctx, source: VectorDataTable, #[default(10.)] dista
result_table
}

#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn count(_: impl Ctx, source: VectorDataTable) -> u64 {
source.instance_iter().count() as u64
}

#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn path_length(_: impl Ctx, source: VectorDataTable) -> f64 {
source
.instance_iter()
.map(|vector_data_instance| {
let transform = vector_data_instance.transform;
vector_data_instance
.instance
.stroke_bezpath_iter()
.map(|mut bezpath| {
bezpath.apply_affine(Affine::new(transform.to_cols_array()));
bezpath.perimeter(DEFAULT_ACCURACY)
})
.sum::<f64>()
})
.sum()
}

#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 {
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
Expand Down Expand Up @@ -1935,6 +1995,7 @@ mod test {
use super::*;
use crate::Node;
use bezier_rs::Bezier;
use kurbo::Rect;
use std::pin::Pin;

#[derive(Clone)]
Expand All @@ -1952,6 +2013,24 @@ mod test {
VectorDataTable::new(VectorData::from_subpath(data))
}

fn create_vector_data_instance(bezpath: BezPath, transform: DAffine2) -> Instance<VectorData> {
let mut instance = VectorData::default();
instance.append_bezpath(bezpath);
Instance {
instance,
transform,
..Default::default()
}
}

fn vector_node_from_instances(data: Vec<Instance<VectorData>>) -> VectorDataTable {
let mut vector_data_table = VectorDataTable::default();
for instance in data {
vector_data_table.push(instance);
}
vector_data_table
}

#[tokio::test]
async fn repeat() {
let direction = DVec2::X * 1.5;
Expand Down Expand Up @@ -2078,12 +2157,23 @@ mod test {
}
}
#[tokio::test]
async fn lengths() {
async fn segment_lengths() {
let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let lengths = subpath_segment_lengths(Footprint::default(), vector_node(subpath)).await;
assert_eq!(lengths, vec![100.]);
}
#[tokio::test]
async fn path_length() {
let bezpath = Rect::new(100., 100., 201., 201.).to_path(DEFAULT_ACCURACY);
let transform = DAffine2::from_scale(DVec2::new(2., 2.));
let instance = create_vector_data_instance(bezpath, transform);
let instances = (0..5).map(|_| instance.clone()).collect::<Vec<Instance<VectorData>>>();

let length = super::path_length(Footprint::default(), vector_node_from_instances(instances)).await;
// 4040. = 101. * 4 (rectangle perimeter) * 2. (scale) * 5. (number of rows)
assert_eq!(length, 4040.);
}
#[tokio::test]
async fn spline() {
let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
let spline = spline.instance_ref_iter().next().unwrap().instance;
Expand Down
Loading