Skip to content

Commit c38987e

Browse files
author
indierusty
committed
refactor
1 parent f5b3f32 commit c38987e

File tree

4 files changed

+107
-96
lines changed

4 files changed

+107
-96
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
mod instance;
22
mod merge_by_distance;
33
pub mod offset_subpath;
4+
pub mod position_on_bezpath;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use kurbo::{BezPath, ParamCurve, Point, Shape};
2+
3+
pub fn position_on_bezpath(bezpath: &BezPath, t: f64, euclidian: bool) -> Point {
4+
if euclidian {
5+
let (segment_index, t) = t_value_to_parametric(&bezpath, BezPathTValue::GlobalEuclidean(t));
6+
let segment = bezpath.get_seg(segment_index + 1).unwrap();
7+
eval_pathseg_euclidian(segment, t, POSITION_ACCURACY)
8+
} else {
9+
let (segment_index, t) = t_value_to_parametric(&bezpath, BezPathTValue::GlobalParametric(t));
10+
let segment = bezpath.get_seg(segment_index + 1).unwrap();
11+
segment.eval(t)
12+
}
13+
}
14+
15+
/// Accuracy to find the position on [kurbo::Bezpath].
16+
const POSITION_ACCURACY: f64 = 1e-3;
17+
/// Accuracy to find the length of the [kurbo::PathSeg].
18+
const PERIMETER_ACCURACY: f64 = 1e-3;
19+
20+
/// Finds the point on the given path segment i.e fractional distance along the segment's total length.
21+
/// It uses a binary search to find the value `t` such that the ratio `length_upto_t / total_length` approximates the input `distance`.
22+
fn eval_pathseg_euclidian(path: kurbo::PathSeg, distance: f64, accuracy: f64) -> kurbo::Point {
23+
let mut low_t = 0.;
24+
let mut hight_t = 1.;
25+
let mut mid_t = 0.5;
26+
27+
let total_length = path.perimeter(accuracy);
28+
29+
if !total_length.is_finite() || total_length <= f64::EPSILON {
30+
return path.start();
31+
}
32+
33+
let distance = distance.clamp(0., 1.);
34+
35+
while hight_t - low_t > accuracy {
36+
let current_length = path.subsegment(0.0..mid_t).perimeter(accuracy);
37+
let current_distance = current_length / total_length;
38+
39+
if current_distance > distance {
40+
hight_t = mid_t;
41+
} else {
42+
low_t = mid_t;
43+
}
44+
mid_t = (hight_t + low_t) / 2.;
45+
}
46+
47+
path.eval(mid_t)
48+
}
49+
50+
/// Converts from a bezpath (composed of multiple segments) to a point along a certain segment represented.
51+
/// The returned tuple represents the segment index and the `t` value along that segment.
52+
/// 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.
53+
fn global_euclidean_to_local_euclidean(bezpath: &kurbo::BezPath, global_t: f64, lengths: &[f64], total_length: f64) -> (usize, f64) {
54+
let mut accumulator = 0.;
55+
for (index, length) in lengths.iter().enumerate() {
56+
let length_ratio = length / total_length;
57+
if (index == 0 || accumulator <= global_t) && global_t <= accumulator + length_ratio {
58+
return (index, ((global_t - accumulator) / length_ratio).clamp(0., 1.));
59+
}
60+
accumulator += length_ratio;
61+
}
62+
(bezpath.segments().count() - 2, 1.)
63+
}
64+
65+
enum BezPathTValue {
66+
GlobalEuclidean(f64),
67+
GlobalParametric(f64),
68+
}
69+
70+
/// Convert a [BezPathTValue] to a parametric `(segment_index, t)` tuple.
71+
/// - Asserts that `t` values contained within the `SubpathTValue` argument lie in the range [0, 1].
72+
fn t_value_to_parametric(bezpath: &kurbo::BezPath, t: BezPathTValue) -> (usize, f64) {
73+
let segment_len = bezpath.segments().count();
74+
assert!(segment_len >= 1);
75+
76+
match t {
77+
BezPathTValue::GlobalEuclidean(t) => {
78+
let lengths = bezpath.segments().map(|bezier| bezier.perimeter(PERIMETER_ACCURACY)).collect::<Vec<f64>>();
79+
let total_length: f64 = lengths.iter().sum();
80+
global_euclidean_to_local_euclidean(&bezpath, t, lengths.as_slice(), total_length)
81+
}
82+
BezPathTValue::GlobalParametric(global_t) => {
83+
assert!((0.0..=1.).contains(&global_t));
84+
85+
if global_t == 1. {
86+
return (segment_len - 1, 1.);
87+
}
88+
89+
let scaled_t = global_t * segment_len as f64;
90+
let segment_index = scaled_t.floor() as usize;
91+
let t = scaled_t - segment_index as f64;
92+
93+
(segment_index, t)
94+
}
95+
}
96+
}

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

+2-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use dyn_any::DynAny;
2-
use glam::{DAffine2, DVec2};
3-
use kurbo::{Affine, Point};
2+
use glam::DVec2;
3+
use kurbo::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,10 +111,3 @@ 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

+8-87
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::algorithms::offset_subpath::offset_subpath;
2-
use super::misc::{CentroidType, daffine2_to_affine, point_to_dvec2};
2+
use super::algorithms::position_on_bezpath::position_on_bezpath;
3+
use super::misc::{CentroidType, point_to_dvec2};
34
use super::style::{Fill, Gradient, GradientStops, Stroke};
45
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
56
use crate::instances::{InstanceMut, Instances};
@@ -12,7 +13,7 @@ use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, Graph
1213
use bezier_rs::{Join, ManipulatorGroup, Subpath, SubpathTValue, TValue};
1314
use core::f64::consts::PI;
1415
use glam::{DAffine2, DVec2};
15-
use kurbo::{BezPath, ParamCurve, Shape};
16+
use kurbo::Affine;
1617
use rand::{Rng, SeedableRng};
1718

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

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-
13341262
/// Determines the position of a point on the path, given by its progress from 0 to 1 along the path.
13351263
/// 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.
13361264
#[node_macro::node(name("Position on Path"), category("Vector"), path(graphene_core::vector))]
@@ -1350,27 +1278,20 @@ async fn position_on_path(
13501278
let vector_data_transform = vector_data.transform();
13511279
let vector_data = vector_data.one_instance().instance;
13521280

1353-
let mut bezpaths: Vec<BezPath> = vector_data.stroke_bezpath_iter().collect();
1281+
let mut bezpaths: Vec<kurbo::BezPath> = vector_data.stroke_bezpath_iter().collect();
13541282
let bezpath_count = bezpaths.len() as f64;
13551283
let progress = progress.clamp(0., bezpath_count);
13561284
let progress = if reverse { bezpath_count - progress } else { progress };
13571285
let index = if progress >= bezpath_count { (bezpath_count - 1.) as usize } else { progress as usize };
13581286

13591287
bezpaths.get_mut(index).map_or(DVec2::ZERO, |bezpath| {
13601288
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-
}
1289+
bezpath.apply_affine(Affine::new(vector_data_transform.to_cols_array()));
1290+
1291+
let position = position_on_bezpath(&bezpath, t, euclidian);
1292+
point_to_dvec2(position)
13711293
})
13721294
}
1373-
/////////////
13741295

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