Trait CurveExt

Source
pub trait CurveExt<T>: Sized + Curve<T> {
Show 18 methods // Provided methods fn sample_iter( &self, iter: impl IntoIterator<Item = f32>, ) -> impl Iterator<Item = Option<T>> { ... } fn sample_iter_unchecked( &self, iter: impl IntoIterator<Item = f32>, ) -> impl Iterator<Item = T> { ... } fn sample_iter_clamped( &self, iter: impl IntoIterator<Item = f32>, ) -> impl Iterator<Item = T> { ... } fn map<S, F>(self, f: F) -> MapCurve<T, S, Self, F> where F: Fn(T) -> S { ... } fn reparametrize<F>( self, domain: Interval, f: F, ) -> ReparamCurve<T, Self, F> where F: Fn(f32) -> f32 { ... } fn reparametrize_linear( self, domain: Interval, ) -> Result<LinearReparamCurve<T, Self>, LinearReparamError> { ... } fn reparametrize_by_curve<C>( self, other: C, ) -> CurveReparamCurve<T, Self, C> where C: Curve<f32> { ... } fn graph(self) -> GraphCurve<T, Self> { ... } fn zip<S, C>( self, other: C, ) -> Result<ZipCurve<T, S, Self, C>, InvalidIntervalError> where C: Curve<S> { ... } fn chain<C>(self, other: C) -> Result<ChainCurve<T, Self, C>, ChainError> where C: Curve<T> { ... } fn reverse(self) -> Result<ReverseCurve<T, Self>, ReverseError> { ... } fn repeat(self, count: usize) -> Result<RepeatCurve<T, Self>, RepeatError> { ... } fn forever(self) -> Result<ForeverCurve<T, Self>, RepeatError> { ... } fn ping_pong(self) -> Result<PingPongCurve<T, Self>, PingPongError> { ... } fn chain_continue<C>( self, other: C, ) -> Result<ContinuationCurve<T, Self, C>, ChainError> where T: VectorSpace, C: Curve<T> { ... } fn samples( &self, samples: usize, ) -> Result<impl Iterator<Item = T>, ResamplingError> { ... } fn by_ref(&self) -> &Self { ... } fn flip<U, V>(self) -> impl Curve<(V, U)> where Self: CurveExt<(U, V)> { ... }
}
Expand description

Extension trait implemented by curves, allowing access to a number of adaptors and convenience methods.

This trait is automatically implemented for all curves that are Sized. In particular, it is implemented for types like Box<dyn Curve<T>>. CurveExt is not dyn-compatible itself.

For more information, see the module-level documentation.

Provided Methods§

Source

fn sample_iter( &self, iter: impl IntoIterator<Item = f32>, ) -> impl Iterator<Item = Option<T>>

Sample a collection of n >= 0 points on this curve at the parameter values t_n, returning None if the point is outside of the curve’s domain.

The samples are returned in the same order as the parameter values t_n were provided and will include all results. This leaves the responsibility for things like filtering and sorting to the user for maximum flexibility.

Source

fn sample_iter_unchecked( &self, iter: impl IntoIterator<Item = f32>, ) -> impl Iterator<Item = T>

Sample a collection of n >= 0 points on this curve at the parameter values t_n, extracting the associated values. This is the unchecked version of sampling, which should only be used if the sample times t_n are already known to lie within the curve’s domain.

Values sampled from outside of a curve’s domain are generally considered invalid; data which is nonsensical or otherwise useless may be returned in such a circumstance, and extrapolation beyond a curve’s domain should not be relied upon.

The samples are returned in the same order as the parameter values t_n were provided and will include all results. This leaves the responsibility for things like filtering and sorting to the user for maximum flexibility.

Source

fn sample_iter_clamped( &self, iter: impl IntoIterator<Item = f32>, ) -> impl Iterator<Item = T>

Sample a collection of n >= 0 points on this curve at the parameter values t_n, clamping t_n to lie inside the domain of the curve.

The samples are returned in the same order as the parameter values t_n were provided and will include all results. This leaves the responsibility for things like filtering and sorting to the user for maximum flexibility.

Source

fn map<S, F>(self, f: F) -> MapCurve<T, S, Self, F>
where F: Fn(T) -> S,

Create a new curve by mapping the values of this curve via a function f; i.e., if the sample at time t for this curve is x, the value at time t on the new curve will be f(x).

Examples found in repository?
examples/animation/easing_functions.rs (line 166)
129fn display_curves(
130    mut gizmos: Gizmos,
131    ease_functions: Query<(&EaseFunctionPlot, &Transform, &Children)>,
132    mut transforms: Query<&mut Transform, Without<EaseFunctionPlot>>,
133    mut ui_text: Single<&mut Text>,
134    time: Res<Time>,
135) {
136    let samples = 100;
137    let duration = 2.5;
138    let time_margin = 0.5;
139
140    let now = ((time.elapsed_secs() % (duration + time_margin * 2.0) - time_margin) / duration)
141        .clamp(0.0, 1.0);
142
143    ui_text.0 = format!("Progress: {:.2}", now);
144
145    for (EaseFunctionPlot(function, color), transform, children) in &ease_functions {
146        let center = transform.translation.xy();
147        let half_size = PLOT_SIZE / 2.0;
148
149        // Draw a box around the curve
150        gizmos.linestrip_2d(
151            [
152                center + half_size,
153                center + half_size * Vec2::new(-1., 1.),
154                center + half_size * Vec2::new(-1., -1.),
155                center + half_size * Vec2::new(1., -1.),
156                center + half_size,
157            ],
158            color.darker(0.4),
159        );
160
161        // Draw the curve
162        let f = EasingCurve::new(0.0, 1.0, *function);
163        let drawn_curve = f
164            .by_ref()
165            .graph()
166            .map(|(x, y)| center - half_size + Vec2::new(x, y) * PLOT_SIZE);
167        gizmos.curve_2d(
168            &drawn_curve,
169            drawn_curve.domain().spaced_points(samples).unwrap(),
170            *color,
171        );
172
173        // Show progress along the curve for the current time
174        let y = f.sample(now).unwrap() * PLOT_SIZE.y;
175        transforms.get_mut(children[0]).unwrap().translation.y = -half_size.y + y;
176        transforms.get_mut(children[1]).unwrap().translation =
177            -half_size.extend(0.0) + Vec3::new(now * PLOT_SIZE.x, y, 0.0);
178
179        // Show horizontal bar at y value
180        gizmos.linestrip_2d(
181            [
182                center - half_size + Vec2::Y * y,
183                center - half_size + Vec2::new(PLOT_SIZE.x, y),
184            ],
185            color.darker(0.2),
186        );
187    }
188}
Source

fn reparametrize<F>(self, domain: Interval, f: F) -> ReparamCurve<T, Self, F>
where F: Fn(f32) -> f32,

Create a new Curve whose parameter space is related to the parameter space of this curve by f. For each time t, the sample from the new curve at time t is the sample from this curve at time f(t). The given domain will be the domain of the new curve. The function f is expected to take domain into self.domain().

Note that this is the opposite of what one might expect intuitively; for example, if this curve has a parameter domain of [0, 1], then stretching the parameter domain to [0, 2] would be performed as follows, dividing by what might be perceived as the scaling factor rather than multiplying:

let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
let scaled_curve = my_curve.reparametrize(interval(0.0, 2.0).unwrap(), |t| t / 2.0);

This kind of linear remapping is provided by the convenience method CurveExt::reparametrize_linear, which requires only the desired domain for the new curve.

§Examples
// Reverse a curve:
let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
let domain = my_curve.domain();
let reversed_curve = my_curve.reparametrize(domain, |t| domain.end() - (t - domain.start()));

// Take a segment of a curve:
let curve_segment = my_curve.reparametrize(interval(0.0, 0.5).unwrap(), |t| 0.5 + t);
Source

fn reparametrize_linear( self, domain: Interval, ) -> Result<LinearReparamCurve<T, Self>, LinearReparamError>

Linearly reparametrize this Curve, producing a new curve whose domain is the given domain instead of the current one. This operation is only valid for curves with bounded domains.

§Errors

If either this curve’s domain or the given domain is unbounded, an error is returned.

Examples found in repository?
examples/animation/eased_motion.rs (line 114)
93    fn create(
94        animation_graphs: &mut Assets<AnimationGraph>,
95        animation_clips: &mut Assets<AnimationClip>,
96    ) -> AnimationInfo {
97        // Create an ID that identifies the text node we're going to animate.
98        let animation_target_name = Name::new("Cube");
99        let animation_target_id = AnimationTargetId::from_name(&animation_target_name);
100
101        // Allocate an animation clip.
102        let mut animation_clip = AnimationClip::default();
103
104        // Each leg of the translation motion should take 3 seconds.
105        let animation_domain = interval(0.0, 3.0).unwrap();
106
107        // The easing curve is parametrized over [0, 1], so we reparametrize it and
108        // then ping-pong, which makes it spend another 3 seconds on the return journey.
109        let translation_curve = EasingCurve::new(
110            vec3(-6., 2., 0.),
111            vec3(6., 2., 0.),
112            EaseFunction::CubicInOut,
113        )
114        .reparametrize_linear(animation_domain)
115        .expect("this curve has bounded domain, so this should never fail")
116        .ping_pong()
117        .expect("this curve has bounded domain, so this should never fail");
118
119        // Something similar for rotation. The repetition here is an illusion caused
120        // by the symmetry of the cube; it rotates on the forward journey and never
121        // rotates back.
122        let rotation_curve = EasingCurve::new(
123            Quat::IDENTITY,
124            Quat::from_rotation_y(FRAC_PI_2),
125            EaseFunction::ElasticInOut,
126        )
127        .reparametrize_linear(interval(0.0, 4.0).unwrap())
128        .expect("this curve has bounded domain, so this should never fail");
129
130        animation_clip.add_curve_to_target(
131            animation_target_id,
132            AnimatableCurve::new(animated_field!(Transform::translation), translation_curve),
133        );
134        animation_clip.add_curve_to_target(
135            animation_target_id,
136            AnimatableCurve::new(animated_field!(Transform::rotation), rotation_curve),
137        );
138
139        // Save our animation clip as an asset.
140        let animation_clip_handle = animation_clips.add(animation_clip);
141
142        // Create an animation graph with that clip.
143        let (animation_graph, animation_node_index) =
144            AnimationGraph::from_clip(animation_clip_handle);
145        let animation_graph_handle = animation_graphs.add(animation_graph);
146
147        AnimationInfo {
148            target_name: animation_target_name,
149            target_id: animation_target_id,
150            graph: animation_graph_handle,
151            node_index: animation_node_index,
152        }
153    }
Source

fn reparametrize_by_curve<C>(self, other: C) -> CurveReparamCurve<T, Self, C>
where C: Curve<f32>,

Reparametrize this Curve by sampling from another curve.

The resulting curve samples at time t by first sampling other at time t, which produces another sample time s which is then used to sample this curve. The domain of the resulting curve is the domain of other.

Source

fn graph(self) -> GraphCurve<T, Self>

Create a new Curve which is the graph of this one; that is, its output echoes the sample time as part of a tuple.

For example, if this curve outputs x at time t, then the produced curve will produce (t, x) at time t. In particular, if this curve is a Curve<T>, the output of this method is a Curve<(f32, T)>.

Examples found in repository?
examples/animation/easing_functions.rs (line 165)
129fn display_curves(
130    mut gizmos: Gizmos,
131    ease_functions: Query<(&EaseFunctionPlot, &Transform, &Children)>,
132    mut transforms: Query<&mut Transform, Without<EaseFunctionPlot>>,
133    mut ui_text: Single<&mut Text>,
134    time: Res<Time>,
135) {
136    let samples = 100;
137    let duration = 2.5;
138    let time_margin = 0.5;
139
140    let now = ((time.elapsed_secs() % (duration + time_margin * 2.0) - time_margin) / duration)
141        .clamp(0.0, 1.0);
142
143    ui_text.0 = format!("Progress: {:.2}", now);
144
145    for (EaseFunctionPlot(function, color), transform, children) in &ease_functions {
146        let center = transform.translation.xy();
147        let half_size = PLOT_SIZE / 2.0;
148
149        // Draw a box around the curve
150        gizmos.linestrip_2d(
151            [
152                center + half_size,
153                center + half_size * Vec2::new(-1., 1.),
154                center + half_size * Vec2::new(-1., -1.),
155                center + half_size * Vec2::new(1., -1.),
156                center + half_size,
157            ],
158            color.darker(0.4),
159        );
160
161        // Draw the curve
162        let f = EasingCurve::new(0.0, 1.0, *function);
163        let drawn_curve = f
164            .by_ref()
165            .graph()
166            .map(|(x, y)| center - half_size + Vec2::new(x, y) * PLOT_SIZE);
167        gizmos.curve_2d(
168            &drawn_curve,
169            drawn_curve.domain().spaced_points(samples).unwrap(),
170            *color,
171        );
172
173        // Show progress along the curve for the current time
174        let y = f.sample(now).unwrap() * PLOT_SIZE.y;
175        transforms.get_mut(children[0]).unwrap().translation.y = -half_size.y + y;
176        transforms.get_mut(children[1]).unwrap().translation =
177            -half_size.extend(0.0) + Vec3::new(now * PLOT_SIZE.x, y, 0.0);
178
179        // Show horizontal bar at y value
180        gizmos.linestrip_2d(
181            [
182                center - half_size + Vec2::Y * y,
183                center - half_size + Vec2::new(PLOT_SIZE.x, y),
184            ],
185            color.darker(0.2),
186        );
187    }
188}
Source

fn zip<S, C>( self, other: C, ) -> Result<ZipCurve<T, S, Self, C>, InvalidIntervalError>
where C: Curve<S>,

Create a new Curve by zipping this curve together with another.

The sample at time t in the new curve is (x, y), where x is the sample of self at time t and y is the sample of other at time t. The domain of the new curve is the intersection of the domains of its constituents.

§Errors

If the domain intersection would be empty, an error is returned instead.

Source

fn chain<C>(self, other: C) -> Result<ChainCurve<T, Self, C>, ChainError>
where C: Curve<T>,

Create a new Curve by composing this curve end-to-start with another, producing another curve with outputs of the same type. The domain of the other curve is translated so that its start coincides with where this curve ends.

§Errors

A ChainError is returned if this curve’s domain doesn’t have a finite end or if other’s domain doesn’t have a finite start.

Source

fn reverse(self) -> Result<ReverseCurve<T, Self>, ReverseError>

Create a new Curve inverting this curve on the x-axis, producing another curve with outputs of the same type, effectively playing backwards starting at self.domain().end() and transitioning over to self.domain().start(). The domain of the new curve is still the same.

§Errors

A ReverseError is returned if this curve’s domain isn’t bounded.

Source

fn repeat(self, count: usize) -> Result<RepeatCurve<T, Self>, RepeatError>

Create a new Curve repeating this curve N times, producing another curve with outputs of the same type. The domain of the new curve will be bigger by a factor of n + 1.

§Notes
  • this doesn’t guarantee a smooth transition from one occurrence of the curve to its next iteration. The curve will make a jump if self.domain().start() != self.domain().end()!
  • for count == 0 the output of this adaptor is basically identical to the previous curve
  • the value at the transitioning points (domain.end() * n for n >= 1) in the results is the value at domain.end() in the original curve
§Errors

A RepeatError is returned if this curve’s domain isn’t bounded.

Source

fn forever(self) -> Result<ForeverCurve<T, Self>, RepeatError>

Create a new Curve repeating this curve forever, producing another curve with outputs of the same type. The domain of the new curve will be unbounded.

§Notes
  • this doesn’t guarantee a smooth transition from one occurrence of the curve to its next iteration. The curve will make a jump if self.domain().start() != self.domain().end()!
  • the value at the transitioning points (domain.end() * n for n >= 1) in the results is the value at domain.end() in the original curve
§Errors

A RepeatError is returned if this curve’s domain isn’t bounded.

Source

fn ping_pong(self) -> Result<PingPongCurve<T, Self>, PingPongError>

Create a new Curve chaining the original curve with its inverse, producing another curve with outputs of the same type. The domain of the new curve will be twice as long. The transition point is guaranteed to not make any jumps.

§Errors

A PingPongError is returned if this curve’s domain isn’t right-finite.

Examples found in repository?
examples/animation/eased_motion.rs (line 116)
93    fn create(
94        animation_graphs: &mut Assets<AnimationGraph>,
95        animation_clips: &mut Assets<AnimationClip>,
96    ) -> AnimationInfo {
97        // Create an ID that identifies the text node we're going to animate.
98        let animation_target_name = Name::new("Cube");
99        let animation_target_id = AnimationTargetId::from_name(&animation_target_name);
100
101        // Allocate an animation clip.
102        let mut animation_clip = AnimationClip::default();
103
104        // Each leg of the translation motion should take 3 seconds.
105        let animation_domain = interval(0.0, 3.0).unwrap();
106
107        // The easing curve is parametrized over [0, 1], so we reparametrize it and
108        // then ping-pong, which makes it spend another 3 seconds on the return journey.
109        let translation_curve = EasingCurve::new(
110            vec3(-6., 2., 0.),
111            vec3(6., 2., 0.),
112            EaseFunction::CubicInOut,
113        )
114        .reparametrize_linear(animation_domain)
115        .expect("this curve has bounded domain, so this should never fail")
116        .ping_pong()
117        .expect("this curve has bounded domain, so this should never fail");
118
119        // Something similar for rotation. The repetition here is an illusion caused
120        // by the symmetry of the cube; it rotates on the forward journey and never
121        // rotates back.
122        let rotation_curve = EasingCurve::new(
123            Quat::IDENTITY,
124            Quat::from_rotation_y(FRAC_PI_2),
125            EaseFunction::ElasticInOut,
126        )
127        .reparametrize_linear(interval(0.0, 4.0).unwrap())
128        .expect("this curve has bounded domain, so this should never fail");
129
130        animation_clip.add_curve_to_target(
131            animation_target_id,
132            AnimatableCurve::new(animated_field!(Transform::translation), translation_curve),
133        );
134        animation_clip.add_curve_to_target(
135            animation_target_id,
136            AnimatableCurve::new(animated_field!(Transform::rotation), rotation_curve),
137        );
138
139        // Save our animation clip as an asset.
140        let animation_clip_handle = animation_clips.add(animation_clip);
141
142        // Create an animation graph with that clip.
143        let (animation_graph, animation_node_index) =
144            AnimationGraph::from_clip(animation_clip_handle);
145        let animation_graph_handle = animation_graphs.add(animation_graph);
146
147        AnimationInfo {
148            target_name: animation_target_name,
149            target_id: animation_target_id,
150            graph: animation_graph_handle,
151            node_index: animation_node_index,
152        }
153    }
Source

fn chain_continue<C>( self, other: C, ) -> Result<ContinuationCurve<T, Self, C>, ChainError>
where T: VectorSpace, C: Curve<T>,

Create a new Curve by composing this curve end-to-start with another, producing another curve with outputs of the same type. The domain of the other curve is translated so that its start coincides with where this curve ends.

Additionally the transition of the samples is guaranteed to make no sudden jumps. This is useful if you really just know about the shapes of your curves and don’t want to deal with stitching them together properly when it would just introduce useless complexity. It is realized by translating the other curve so that its start sample point coincides with the current curves’ end sample point.

§Errors

A ChainError is returned if this curve’s domain doesn’t have a finite end or if other’s domain doesn’t have a finite start.

Source

fn samples( &self, samples: usize, ) -> Result<impl Iterator<Item = T>, ResamplingError>

Extract an iterator over evenly-spaced samples from this curve.

§Errors

If samples is less than 2 or if this curve has unbounded domain, a ResamplingError is returned.

Source

fn by_ref(&self) -> &Self

Borrow this curve rather than taking ownership of it. This is essentially an alias for a prefix &; the point is that intermediate operations can be performed while retaining access to the original curve.

§Example
let my_curve = FunctionCurve::new(Interval::UNIT, |t| t * t + 1.0);

// Borrow `my_curve` long enough to resample a mapped version. Note that `map` takes
// ownership of its input.
let samples = my_curve.by_ref().map(|x| x * 2.0).resample_auto(100).unwrap();

// Do something else with `my_curve` since we retained ownership:
let new_curve = my_curve.reparametrize_linear(interval(-1.0, 1.0).unwrap()).unwrap();
Examples found in repository?
examples/animation/easing_functions.rs (line 164)
129fn display_curves(
130    mut gizmos: Gizmos,
131    ease_functions: Query<(&EaseFunctionPlot, &Transform, &Children)>,
132    mut transforms: Query<&mut Transform, Without<EaseFunctionPlot>>,
133    mut ui_text: Single<&mut Text>,
134    time: Res<Time>,
135) {
136    let samples = 100;
137    let duration = 2.5;
138    let time_margin = 0.5;
139
140    let now = ((time.elapsed_secs() % (duration + time_margin * 2.0) - time_margin) / duration)
141        .clamp(0.0, 1.0);
142
143    ui_text.0 = format!("Progress: {:.2}", now);
144
145    for (EaseFunctionPlot(function, color), transform, children) in &ease_functions {
146        let center = transform.translation.xy();
147        let half_size = PLOT_SIZE / 2.0;
148
149        // Draw a box around the curve
150        gizmos.linestrip_2d(
151            [
152                center + half_size,
153                center + half_size * Vec2::new(-1., 1.),
154                center + half_size * Vec2::new(-1., -1.),
155                center + half_size * Vec2::new(1., -1.),
156                center + half_size,
157            ],
158            color.darker(0.4),
159        );
160
161        // Draw the curve
162        let f = EasingCurve::new(0.0, 1.0, *function);
163        let drawn_curve = f
164            .by_ref()
165            .graph()
166            .map(|(x, y)| center - half_size + Vec2::new(x, y) * PLOT_SIZE);
167        gizmos.curve_2d(
168            &drawn_curve,
169            drawn_curve.domain().spaced_points(samples).unwrap(),
170            *color,
171        );
172
173        // Show progress along the curve for the current time
174        let y = f.sample(now).unwrap() * PLOT_SIZE.y;
175        transforms.get_mut(children[0]).unwrap().translation.y = -half_size.y + y;
176        transforms.get_mut(children[1]).unwrap().translation =
177            -half_size.extend(0.0) + Vec3::new(now * PLOT_SIZE.x, y, 0.0);
178
179        // Show horizontal bar at y value
180        gizmos.linestrip_2d(
181            [
182                center - half_size + Vec2::Y * y,
183                center - half_size + Vec2::new(PLOT_SIZE.x, y),
184            ],
185            color.darker(0.2),
186        );
187    }
188}
Source

fn flip<U, V>(self) -> impl Curve<(V, U)>
where Self: CurveExt<(U, V)>,

Flip this curve so that its tuple output is arranged the other way.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§

Source§

impl<C, T> CurveExt<T> for C
where C: Curve<T>,