Skip to main content

path_traits/
sampler.rs

1//! Evenly-spaced path sampling.
2//!
3//! Free functions for generating sample points along a path:
4//!
5//! - [`equidistant`] - samples at fixed arc-length steps
6//! - [`n_samples`] - samples at `n` evenly-spaced arc-length positions
7//! - [`uniform_t`] - samples at `n` evenly-spaced normalized-parameter values
8
9use crate::{ParametricPath, Path, Scalar};
10
11/// Sample a path at equidistant arc-length intervals.
12///
13/// Yields points at `s = 0, step, 2*step, …` until `s > length`. The final
14/// sample may exceed `length` by less than `step`.
15pub fn equidistant<P: Path>(
16    path: &P,
17    step: P::Scalar,
18) -> impl Iterator<Item = Result<P::Point, P::Error>> + '_ {
19    let length = path.length();
20    let zero = P::Scalar::zero();
21    let mut s = zero;
22    core::iter::from_fn(move || {
23        if s > length {
24            None
25        } else {
26            let result = path.sample_at(s);
27            s = s + step;
28            Some(result)
29        }
30    })
31}
32
33/// Sample a path at `n` evenly-spaced arc-length positions.
34///
35/// The samples are at `s = 0, length/(n-1), 2*length/(n-1), …, length`.
36/// For `n == 1`, returns a single sample at `s = 0`.
37pub fn n_samples<P: Path>(
38    path: &P,
39    n: usize,
40) -> impl Iterator<Item = Result<P::Point, P::Error>> + '_ {
41    let length = path.length();
42    let zero = P::Scalar::zero();
43    let one = P::Scalar::one();
44    let n_scalar = P::Scalar::from_usize(n);
45    let step = if n > 1 {
46        length / (n_scalar - one)
47    } else {
48        zero
49    };
50    (0..n).map(move |i| {
51        let s = if n == 1 {
52            zero
53        } else {
54            P::Scalar::from_usize(i) * step
55        };
56        path.sample_at(s)
57    })
58}
59
60/// Sample a parametric path at `n` evenly-spaced `t` values in `[0, 1]`.
61///
62/// The samples are at `t = 0, 1/(n-1), 2/(n-1), …, 1`.
63/// For `n == 1`, returns a single sample at `t = 0`.
64pub fn uniform_t<P: ParametricPath>(
65    path: &P,
66    n: usize,
67) -> impl Iterator<Item = Result<P::Point, P::Error>> + '_ {
68    let zero = P::Scalar::zero();
69    let one = P::Scalar::one();
70    let n_scalar = P::Scalar::from_usize(n);
71    let dt = if n > 1 { one / (n_scalar - one) } else { zero };
72    (0..n).map(move |i| {
73        let t = if n == 1 {
74            zero
75        } else {
76            P::Scalar::from_usize(i) * dt
77        };
78        path.sample_t(t)
79    })
80}