Skip to content

Commit f5b3f32

Browse files
author
indierusty
committed
rough refactor of Position on Path node
1 parent a29802d commit f5b3f32

File tree

2 files changed

+103
-13
lines changed

2 files changed

+103
-13
lines changed

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use dyn_any::DynAny;
2-
use glam::DVec2;
3-
use kurbo::Point;
2+
use glam::{DAffine2, DVec2};
3+
use kurbo::{Affine, Point};
44

55
/// Represents different ways of calculating the centroid.
66
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
@@ -111,3 +111,10 @@ pub fn point_to_dvec2(point: Point) -> DVec2 {
111111
pub fn dvec2_to_point(value: DVec2) -> Point {
112112
Point { x: value.x, y: value.y }
113113
}
114+
115+
pub fn daffine2_to_affine(value: DAffine2) -> Affine {
116+
let x_axis = value.matrix2.x_axis;
117+
let y_axis = value.matrix2.y_axis;
118+
let translation = value.translation;
119+
Affine::new([x_axis.x, x_axis.y, y_axis.x, y_axis.y, translation.x, translation.y])
120+
}

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

Lines changed: 94 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::algorithms::offset_subpath::offset_subpath;
2-
use super::misc::CentroidType;
2+
use super::misc::{CentroidType, daffine2_to_affine, point_to_dvec2};
33
use super::style::{Fill, Gradient, GradientStops, Stroke};
44
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
55
use crate::instances::{InstanceMut, Instances};
@@ -12,6 +12,7 @@ use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, Graph
1212
use bezier_rs::{Join, ManipulatorGroup, Subpath, SubpathTValue, TValue};
1313
use core::f64::consts::PI;
1414
use glam::{DAffine2, DVec2};
15+
use kurbo::{BezPath, ParamCurve, Shape};
1516
use rand::{Rng, SeedableRng};
1617

1718
/// Implemented for types that can be converted to an iterator of vector data.
@@ -1257,6 +1258,79 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
12571258
result
12581259
}
12591260

1261+
////////////// TODO: clean up and refactor.
1262+
fn eval_pathseg_euclidian(path: kurbo::PathSeg, distance: f64, accuracy: f64) -> kurbo::Point {
1263+
let mut low_t = 0.;
1264+
let mut hight_t = 1.;
1265+
let mut mid_t = 0.5;
1266+
1267+
let total_length = path.perimeter(accuracy);
1268+
while hight_t - low_t > accuracy {
1269+
let cur_len = path.subsegment(0.0..mid_t).perimeter(accuracy);
1270+
let cur_distance = cur_len / total_length;
1271+
1272+
if cur_distance > distance {
1273+
hight_t = mid_t;
1274+
} else {
1275+
low_t = mid_t;
1276+
}
1277+
mid_t = (hight_t + low_t) / 2.;
1278+
}
1279+
1280+
path.eval(mid_t)
1281+
}
1282+
1283+
/// Converts from a subpath (composed of multiple segments) to a point along a certain segment represented.
1284+
/// The returned tuple represents the segment index and the `t` value along that segment.
1285+
/// Both the input global `t` value and the output `t` value are in euclidean space, meaning there is a constant rate of change along the arc length.
1286+
pub fn global_euclidean_to_local_euclidean(bezpath: &kurbo::BezPath, global_t: f64, lengths: &[f64], total_length: f64) -> (usize, f64) {
1287+
let mut accumulator = 0.;
1288+
for (index, length) in lengths.iter().enumerate() {
1289+
let length_ratio = length / total_length;
1290+
if (index == 0 || accumulator <= global_t) && global_t <= accumulator + length_ratio {
1291+
return (index, ((global_t - accumulator) / length_ratio).clamp(0., 1.));
1292+
}
1293+
accumulator += length_ratio;
1294+
}
1295+
(bezpath.segments().count() - 2, 1.)
1296+
}
1297+
/// Default error bound for `t_value_to_parametric` function when TValue argument is Euclidean
1298+
pub const DEFAULT_EUCLIDEAN_ERROR_BOUND: f64 = 0.001;
1299+
1300+
/// Convert a [SubpathTValue] to a parametric `(segment_index, t)` tuple.
1301+
/// - Asserts that `t` values contained within the `SubpathTValue` argument lie in the range [0, 1].
1302+
/// - If the argument is a variant containing a `segment_index`, asserts that the index references a valid segment on the curve.
1303+
// fn t_value_to_parametric(bezpath: &kurbo::BezPath, t: SubpathTValue) -> (usize, f64) {
1304+
fn segment_index_t_value(bezpath: &kurbo::BezPath, t: SubpathTValue) -> (usize, f64) {
1305+
let segment_len = bezpath.segments().count();
1306+
assert!(segment_len >= 1);
1307+
1308+
match t {
1309+
SubpathTValue::GlobalEuclidean(t) => {
1310+
let lengths = bezpath.segments().map(|bezier| bezier.perimeter(0.01)).collect::<Vec<f64>>();
1311+
let total_length: f64 = lengths.iter().sum();
1312+
let (segment_index, segment_t_euclidean) = global_euclidean_to_local_euclidean(&bezpath, t, lengths.as_slice(), total_length);
1313+
// let segment_t_parametric = bezpath.get_seg(segment_index).unwrap().euclidean_to_parametric(segment_t_euclidean, DEFAULT_EUCLIDEAN_ERROR_BOUND);
1314+
// (segment_index, segment_t_parametric)
1315+
(segment_index, segment_t_euclidean)
1316+
}
1317+
SubpathTValue::GlobalParametric(global_t) => {
1318+
assert!((0.0..=1.).contains(&global_t));
1319+
1320+
if global_t == 1. {
1321+
return (segment_len - 1, 1.);
1322+
}
1323+
1324+
let scaled_t = global_t * segment_len as f64;
1325+
let segment_index = scaled_t.floor() as usize;
1326+
let t = scaled_t - segment_index as f64;
1327+
1328+
(segment_index, t)
1329+
}
1330+
_ => unreachable!(),
1331+
}
1332+
}
1333+
12601334
/// Determines the position of a point on the path, given by its progress from 0 to 1 along the path.
12611335
/// 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.
12621336
#[node_macro::node(name("Position on Path"), category("Vector"), path(graphene_core::vector))]
@@ -1276,18 +1350,27 @@ async fn position_on_path(
12761350
let vector_data_transform = vector_data.transform();
12771351
let vector_data = vector_data.one_instance().instance;
12781352

1279-
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
1280-
let progress = progress.clamp(0., subpaths_count);
1281-
let progress = if reverse { subpaths_count - progress } else { progress };
1282-
let index = if progress >= subpaths_count { (subpaths_count - 1.) as usize } else { progress as usize };
1283-
1284-
vector_data.stroke_bezier_paths().nth(index).map_or(DVec2::ZERO, |mut subpath| {
1285-
subpath.apply_transform(vector_data_transform);
1286-
1287-
let t = if progress == subpaths_count { 1. } else { progress.fract() };
1288-
subpath.evaluate(if euclidian { SubpathTValue::GlobalEuclidean(t) } else { SubpathTValue::GlobalParametric(t) })
1353+
let mut bezpaths: Vec<BezPath> = vector_data.stroke_bezpath_iter().collect();
1354+
let bezpath_count = bezpaths.len() as f64;
1355+
let progress = progress.clamp(0., bezpath_count);
1356+
let progress = if reverse { bezpath_count - progress } else { progress };
1357+
let index = if progress >= bezpath_count { (bezpath_count - 1.) as usize } else { progress as usize };
1358+
1359+
bezpaths.get_mut(index).map_or(DVec2::ZERO, |bezpath| {
1360+
let t = if progress == bezpath_count { 1. } else { progress.fract() };
1361+
bezpath.apply_affine(daffine2_to_affine(vector_data_transform));
1362+
if euclidian {
1363+
let (seg_index, t) = segment_index_t_value(&bezpath, SubpathTValue::GlobalEuclidean(t));
1364+
let seg = bezpath.get_seg(1 + seg_index).unwrap();
1365+
point_to_dvec2(eval_pathseg_euclidian(seg, t, 0.001))
1366+
} else {
1367+
let (seg_index, t) = segment_index_t_value(&bezpath, SubpathTValue::GlobalParametric(t));
1368+
let seg = bezpath.get_seg(1 + seg_index).unwrap();
1369+
point_to_dvec2(seg.eval(t))
1370+
}
12891371
})
12901372
}
1373+
/////////////
12911374

12921375
/// Determines the angle of the tangent at a point on the path, given by its progress from 0 to 1 along the path.
12931376
/// 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.

0 commit comments

Comments
 (0)