From c8167c1276dd448906d8ef596b22890b063fd676 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Wed, 31 May 2023 16:57:37 +0200 Subject: [PATCH] Add `CubicCurve::segment_count` + `iter_samples` adjustment (#8711) ## Objective - Provide a way to use `CubicCurve` non-iter methods - Accept a `FnMut` over a `fn` pointer on `iter_samples` - Improve `build_*_cubic_100_points` benchmark by -45% (this means they are twice as fast) ### Solution Previously, the only way to iterate over an evenly spaced set of points on a `CubicCurve` was to use one of the `iter_*` methods. The return value of those methods were bound by `&self` lifetime, making them unusable in certain contexts. Furthermore, other `CubicCurve` methods (`position`, `velocity`, `acceleration`) required normalizing `t` over the `CubicCurve`'s internal segment count. There were no way to access this segment count, making those methods pretty much unusable. The newly added `segment_count` allows accessing the segment count. `iter_samples` used to accept a `fn`, a function pointer. This is surprising and contrary to the rust stdlib APIs, which accept `Fn` traits for `Iterator` combinators. `iter_samples` now accepts a `FnMut`. I don't trust a bit the bevy benchmark suit, but according to it, this doubles (-45%) the performance on the `build_pos_cubic_100_points` and `build_accel_cubic_100_points` benchmarks. --- ## Changelog - Added the `CubicCurve::segments` method to access the underlying segments of a cubic curve - Allow closures as `CubicCurve::iter_samples` `sample_function` argument. --- crates/bevy_math/src/cubic_splines.rs | 43 +++++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index bb5a988557..c0ff6cf272 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -472,22 +472,39 @@ impl CubicCurve

{ /// A flexible iterator used to sample curves with arbitrary functions. /// /// This splits the curve into `subdivisions` of evenly spaced `t` values across the - /// length of the curve from start (t = 0) to end (t = 1), returning an iterator that evaluates - /// the curve with the supplied `sample_function` at each `t`. + /// length of the curve from start (t = 0) to end (t = n), where `n = self.segment_count()`, + /// returning an iterator evaluating the curve with the supplied `sample_function` at each `t`. /// - /// Given `subdivisions = 2`, this will split the curve into two lines, or three points, and - /// return an iterator over those three points, one at the start, middle, and end. + /// For `subdivisions = 2`, this will split the curve into two lines, or three points, and + /// return an iterator with 3 items, the three points, one at the start, middle, and end. #[inline] - pub fn iter_samples( - &self, + pub fn iter_samples<'a, 'b: 'a>( + &'b self, subdivisions: usize, - sample_function: fn(&Self, f32) -> P, - ) -> impl Iterator + '_ { - (0..=subdivisions).map(move |i| { - let segments = self.segments.len() as f32; - let t = i as f32 / subdivisions as f32 * segments; - sample_function(self, t) - }) + mut sample_function: impl FnMut(&Self, f32) -> P + 'a, + ) -> impl Iterator + 'a { + self.iter_uniformly(subdivisions) + .map(move |t| sample_function(self, t)) + } + + /// An iterator that returns values of `t` uniformly spaced over `0..=subdivisions`. + #[inline] + fn iter_uniformly(&self, subdivisions: usize) -> impl Iterator { + let segments = self.segments.len() as f32; + let step = segments / subdivisions as f32; + (0..=subdivisions).map(move |i| i as f32 * step) + } + + /// The list of segments contained in this `CubicCurve`. + /// + /// This spline's global `t` value is equal to how many segments it has. + /// + /// All method accepting `t` on `CubicCurve` depends on the global `t`. + /// When sampling over the entire curve, you should either use one of the + /// `iter_*` methods or account for the segment count using `curve.segments().len()`. + #[inline] + pub fn segments(&self) -> &[CubicSegment

] { + &self.segments } /// Iterate over the curve split into `subdivisions`, sampling the position at each step.