Skip to content

Commit fa21385

Browse files
authored
New nodes: 'Position on Path' and 'Tangent on Path' (#2588)
1 parent c4484cb commit fa21385

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,79 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
12371237
result
12381238
}
12391239

1240+
/// Determines the position of a point on the path, given by its progress from 0 to 1 along the path.
1241+
/// 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.
1242+
#[node_macro::node(name("Position on Path"), category("Vector"), path(graphene_core::vector))]
1243+
async fn position_on_path(
1244+
_: impl Ctx,
1245+
/// The path to traverse.
1246+
vector_data: VectorDataTable,
1247+
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
1248+
progress: f64,
1249+
/// Swap the direction of the path.
1250+
reverse: bool,
1251+
/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.
1252+
parameterized_distance: bool,
1253+
) -> DVec2 {
1254+
let euclidian = !parameterized_distance;
1255+
1256+
let vector_data_transform = vector_data.transform();
1257+
let vector_data = vector_data.one_instance().instance;
1258+
1259+
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
1260+
let progress = progress.clamp(0., subpaths_count);
1261+
let progress = if reverse { subpaths_count - progress } else { progress };
1262+
let index = if progress >= subpaths_count { (subpaths_count - 1.) as usize } else { progress as usize };
1263+
1264+
vector_data.stroke_bezier_paths().nth(index).map_or(DVec2::ZERO, |mut subpath| {
1265+
subpath.apply_transform(vector_data_transform);
1266+
1267+
let t = if progress == subpaths_count { 1. } else { progress.fract() };
1268+
subpath.evaluate(if euclidian { SubpathTValue::GlobalEuclidean(t) } else { SubpathTValue::GlobalParametric(t) })
1269+
})
1270+
}
1271+
1272+
/// Determines the angle of the tangent at a point on the path, given by its progress from 0 to 1 along the path.
1273+
/// 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.
1274+
#[node_macro::node(name("Tangent on Path"), category("Vector"), path(graphene_core::vector))]
1275+
async fn tangent_on_path(
1276+
_: impl Ctx,
1277+
/// The path to traverse.
1278+
vector_data: VectorDataTable,
1279+
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
1280+
progress: f64,
1281+
/// Swap the direction of the path.
1282+
reverse: bool,
1283+
/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.
1284+
parameterized_distance: bool,
1285+
) -> f64 {
1286+
let euclidian = !parameterized_distance;
1287+
1288+
let vector_data_transform = vector_data.transform();
1289+
let vector_data = vector_data.one_instance().instance;
1290+
1291+
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
1292+
let progress = progress.clamp(0., subpaths_count);
1293+
let progress = if reverse { subpaths_count - progress } else { progress };
1294+
let index = if progress >= subpaths_count { (subpaths_count - 1.) as usize } else { progress as usize };
1295+
1296+
vector_data.stroke_bezier_paths().nth(index).map_or(0., |mut subpath| {
1297+
subpath.apply_transform(vector_data_transform);
1298+
1299+
let t = if progress == subpaths_count { 1. } else { progress.fract() };
1300+
let mut tangent = subpath.tangent(if euclidian { SubpathTValue::GlobalEuclidean(t) } else { SubpathTValue::GlobalParametric(t) });
1301+
if tangent == DVec2::ZERO {
1302+
let t = t + if t > 0.5 { -0.001 } else { 0.001 };
1303+
tangent = subpath.tangent(if euclidian { SubpathTValue::GlobalEuclidean(t) } else { SubpathTValue::GlobalParametric(t) });
1304+
}
1305+
if tangent == DVec2::ZERO {
1306+
return 0.;
1307+
}
1308+
1309+
-tangent.angle_to(if reverse { -DVec2::X } else { DVec2::X })
1310+
})
1311+
}
1312+
12401313
#[node_macro::node(category(""), path(graphene_core::vector))]
12411314
async fn poisson_disk_points(
12421315
_: impl Ctx,

website/other/bezier-rs-demos/src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ export function getSubpathDemoArgs(): SubpathDemoArgs[] {
170170
export const tSliderOptions = {
171171
variable: "t",
172172
inputType: "slider",
173-
min: 0,
174-
max: 1,
173+
min: -0.01,
174+
max: 1.01,
175175
step: 0.01,
176176
default: 0.5,
177177
};

0 commit comments

Comments
 (0)