Skip to content

Commit 9302781

Browse files
indierustyKeavon
andauthored
New nodes: 'Path Length', 'Count', and 'Split Path' (#2731)
* impl path length node * test 'Path Length' node implementation * improve test * impl 'Count' node to return the number of instance in a VectorDataTable instances * impl 'Split Path' node * don't split if t is close of 0 or 1 or the bezpath is empty * write comments * preserve the style on vector data in 'Split Path' node implementation * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent b5975e9 commit 9302781

File tree

2 files changed

+145
-3
lines changed

2 files changed

+145
-3
lines changed

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,55 @@ use crate::vector::misc::dvec2_to_point;
33
use glam::DVec2;
44
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, Rect, Shape};
55

6+
/// Splits the [`BezPath`] at `t` value which lie in the range of [0, 1].
7+
/// Returns [`None`] if the given [`BezPath`] has no segments or `t` is within f64::EPSILON of 0 or 1.
8+
pub fn split_bezpath(bezpath: &BezPath, t: f64, euclidian: bool) -> Option<(BezPath, BezPath)> {
9+
if t <= f64::EPSILON || (1. - t) <= f64::EPSILON || bezpath.segments().count() == 0 {
10+
return None;
11+
}
12+
13+
// Get the segment which lies at the split.
14+
let (segment_index, t) = t_value_to_parametric(bezpath, t, euclidian, None);
15+
let segment = bezpath.get_seg(segment_index + 1).unwrap();
16+
17+
// Divide the segment.
18+
let first_segment = segment.subsegment(0.0..t);
19+
let second_segment = segment.subsegment(t..1.);
20+
21+
let mut first_bezpath = BezPath::new();
22+
let mut second_bezpath = BezPath::new();
23+
24+
// Append the segments up to the subdividing segment from original bezpath to first bezpath.
25+
for segment in bezpath.segments().take(segment_index) {
26+
if first_bezpath.elements().is_empty() {
27+
first_bezpath.move_to(segment.start());
28+
}
29+
first_bezpath.push(segment.as_path_el());
30+
}
31+
32+
// Append the first segment of the subdivided segment.
33+
if first_bezpath.elements().is_empty() {
34+
first_bezpath.move_to(first_segment.start());
35+
}
36+
first_bezpath.push(first_segment.as_path_el());
37+
38+
// Append the second segment of the subdivided segment in the second bezpath.
39+
if second_bezpath.elements().is_empty() {
40+
second_bezpath.move_to(second_segment.start());
41+
}
42+
second_bezpath.push(second_segment.as_path_el());
43+
44+
// Append the segments after the subdividing segment from original bezpath to second bezpath.
45+
for segment in bezpath.segments().skip(segment_index + 1) {
46+
if second_bezpath.elements().is_empty() {
47+
second_bezpath.move_to(segment.start());
48+
}
49+
second_bezpath.push(segment.as_path_el());
50+
}
51+
52+
Some((first_bezpath, second_bezpath))
53+
}
54+
655
pub fn position_on_bezpath(bezpath: &BezPath, t: f64, euclidian: bool, segments_length: Option<&[f64]>) -> Point {
756
let (segment_index, t) = t_value_to_parametric(bezpath, t, euclidian, segments_length);
857
bezpath.get_seg(segment_index + 1).unwrap().eval(t)

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

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use super::algorithms::bezpath_algorithms::{self, position_on_bezpath, sample_points_on_bezpath, tangent_on_bezpath};
1+
use super::algorithms::bezpath_algorithms::{self, position_on_bezpath, sample_points_on_bezpath, split_bezpath, tangent_on_bezpath};
22
use super::algorithms::offset_subpath::offset_subpath;
33
use super::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
44
use super::misc::{CentroidType, point_to_dvec2};
55
use super::style::{Fill, Gradient, GradientStops, Stroke};
66
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
77
use crate::instances::{Instance, InstanceMut, Instances};
8-
use crate::raster_types::{CPU, RasterDataTable};
8+
use crate::raster_types::{CPU, GPU, RasterDataTable};
99
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
1010
use crate::renderer::GraphicElementRendered;
1111
use crate::transform::{Footprint, ReferencePoint, Transform};
@@ -1314,6 +1314,45 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
13141314
result_table
13151315
}
13161316

1317+
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
1318+
async fn split_path(_: impl Ctx, mut vector_data: VectorDataTable, t_value: f64, parameterized_distance: bool, reverse: bool) -> VectorDataTable {
1319+
let euclidian = !parameterized_distance;
1320+
1321+
let bezpaths = vector_data
1322+
.instance_ref_iter()
1323+
.enumerate()
1324+
.flat_map(|(instance_row_index, vector_data)| vector_data.instance.stroke_bezpath_iter().map(|bezpath| (instance_row_index, bezpath)).collect::<Vec<_>>())
1325+
.collect::<Vec<_>>();
1326+
1327+
let bezpath_count = bezpaths.len() as f64;
1328+
let t_value = t_value.clamp(0., bezpath_count);
1329+
let t_value = if reverse { bezpath_count - t_value } else { t_value };
1330+
let index = if t_value >= bezpath_count { (bezpath_count - 1.) as usize } else { t_value as usize };
1331+
1332+
if let Some((instance_row_index, bezpath)) = bezpaths.get(index).cloned() {
1333+
let mut result_vector_data = VectorData {
1334+
style: vector_data.get(instance_row_index).unwrap().instance.style.clone(),
1335+
..Default::default()
1336+
};
1337+
1338+
for (_, (_, bezpath)) in bezpaths.iter().enumerate().filter(|(i, (ri, _))| *i != index && *ri == instance_row_index) {
1339+
result_vector_data.append_bezpath(bezpath.clone());
1340+
}
1341+
let t = if t_value == bezpath_count { 1. } else { t_value.fract() };
1342+
1343+
if let Some((first, second)) = split_bezpath(&bezpath, t, euclidian) {
1344+
result_vector_data.append_bezpath(first);
1345+
result_vector_data.append_bezpath(second);
1346+
} else {
1347+
result_vector_data.append_bezpath(bezpath);
1348+
}
1349+
1350+
*vector_data.get_mut(instance_row_index).unwrap().instance = result_vector_data;
1351+
}
1352+
1353+
vector_data
1354+
}
1355+
13171356
/// Determines the position of a point on the path, given by its progress from 0 to 1 along the path.
13181357
/// 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.
13191358
#[node_macro::node(name("Position on Path"), category("Vector"), path(graphene_core::vector))]
@@ -1870,6 +1909,29 @@ fn point_inside(_: impl Ctx, source: VectorDataTable, point: DVec2) -> bool {
18701909
source.instance_iter().any(|instance| instance.instance.check_point_inside_shape(instance.transform, point))
18711910
}
18721911

1912+
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
1913+
async fn count_elements<I>(_: impl Ctx, #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>, RasterDataTable<GPU>)] source: Instances<I>) -> u64 {
1914+
source.instance_iter().count() as u64
1915+
}
1916+
1917+
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
1918+
async fn path_length(_: impl Ctx, source: VectorDataTable) -> f64 {
1919+
source
1920+
.instance_iter()
1921+
.map(|vector_data_instance| {
1922+
let transform = vector_data_instance.transform;
1923+
vector_data_instance
1924+
.instance
1925+
.stroke_bezpath_iter()
1926+
.map(|mut bezpath| {
1927+
bezpath.apply_affine(Affine::new(transform.to_cols_array()));
1928+
bezpath.perimeter(DEFAULT_ACCURACY)
1929+
})
1930+
.sum::<f64>()
1931+
})
1932+
.sum()
1933+
}
1934+
18731935
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
18741936
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 {
18751937
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
@@ -1942,6 +2004,7 @@ mod test {
19422004
use super::*;
19432005
use crate::Node;
19442006
use bezier_rs::Bezier;
2007+
use kurbo::Rect;
19452008
use std::pin::Pin;
19462009

19472010
#[derive(Clone)]
@@ -1959,6 +2022,24 @@ mod test {
19592022
VectorDataTable::new(VectorData::from_subpath(data))
19602023
}
19612024

2025+
fn create_vector_data_instance(bezpath: BezPath, transform: DAffine2) -> Instance<VectorData> {
2026+
let mut instance = VectorData::default();
2027+
instance.append_bezpath(bezpath);
2028+
Instance {
2029+
instance,
2030+
transform,
2031+
..Default::default()
2032+
}
2033+
}
2034+
2035+
fn vector_node_from_instances(data: Vec<Instance<VectorData>>) -> VectorDataTable {
2036+
let mut vector_data_table = VectorDataTable::default();
2037+
for instance in data {
2038+
vector_data_table.push(instance);
2039+
}
2040+
vector_data_table
2041+
}
2042+
19622043
#[tokio::test]
19632044
async fn repeat() {
19642045
let direction = DVec2::X * 1.5;
@@ -2085,12 +2166,24 @@ mod test {
20852166
}
20862167
}
20872168
#[tokio::test]
2088-
async fn lengths() {
2169+
async fn segment_lengths() {
20892170
let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
20902171
let lengths = subpath_segment_lengths(Footprint::default(), vector_node(subpath)).await;
20912172
assert_eq!(lengths, vec![100.]);
20922173
}
20932174
#[tokio::test]
2175+
async fn path_length() {
2176+
let bezpath = Rect::new(100., 100., 201., 201.).to_path(DEFAULT_ACCURACY);
2177+
let transform = DAffine2::from_scale(DVec2::new(2., 2.));
2178+
let instance = create_vector_data_instance(bezpath, transform);
2179+
let instances = (0..5).map(|_| instance.clone()).collect::<Vec<Instance<VectorData>>>();
2180+
2181+
let length = super::path_length(Footprint::default(), vector_node_from_instances(instances)).await;
2182+
2183+
// 4040 equals 101 * 4 (rectangle perimeter) * 2 (scale) * 5 (number of rows)
2184+
assert_eq!(length, 4040.);
2185+
}
2186+
#[tokio::test]
20942187
async fn spline() {
20952188
let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
20962189
let spline = spline.instance_ref_iter().next().unwrap().instance;

0 commit comments

Comments
 (0)