bevy/benches/benches/bevy_math/bezier.rs
Ruslan Baynazarov c7531074bc
Improve cubic segment bezier functionality (#17645)
# Objective

- Fixes #17642

## Solution

- Implemented method `new_bezier(points: [P; 4]) -> Self` for
`CubicSegment<P>`
- Old implementation of `new_bezier` is now `new_bezier_easing(p1: impl
Into<Vec2>, p2: impl Into<Vec2>) -> Self` (**breaking change**)
- ~~added method `new_bezier_with_anchor`, which can make a bezier curve
between two points with one control anchor~~
- added methods `iter_positions`, `iter_velocities`,
`iter_accelerations`, the same as in `CubicCurve` (**copied code,
potentially can be reduced)**
- bezier creation logic is moved from `CubicCurve` to `CubicSegment`,
removing the unneeded allocation

## Testing

- Did you test these changes? If so, how?
  - Run tests inside `crates/bevy_math/`
  - Tested the functionality in my project
- Are there any parts that need more testing?
  - Did not run `cargo test` on the whole bevy directory because of OOM
- Performance improvements are expected when creating `CubicCurve` with
`new_bezier` and `new_bezier_easing`, but not tested
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
  - Use in any code that works created `CubicCurve::new_bezier`
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
  - I don't think relevant

---

## Showcase

```rust
// Imagine a car goes towards a local target

// Create a simple `CubicSegment`, without using heap
let planned_path = CubicSegment::new_bezier([
    car_pos,
    car_pos + car_dir * turn_radius,
    target_point - target_dir * turn_radius,
    target_point,
]);

// Check if the planned path itersect other entities
for pos in planned_path.iter_positions(8) {
   // do some collision checks
}
```

## Migration Guide

> This section is optional. If there are no breaking changes, you can
delete this section.

- Replace `CubicCurve::new_bezier` with `CubicCurve::new_bezier_easing`
2025-02-26 20:36:54 +00:00

103 lines
2.7 KiB
Rust

use benches::bench;
use bevy_math::{prelude::*, VectorSpace};
use core::hint::black_box;
use criterion::{
criterion_group, measurement::Measurement, BatchSize, BenchmarkGroup, BenchmarkId, Criterion,
};
criterion_group!(benches, segment_ease, curve_position, curve_iter_positions);
fn segment_ease(c: &mut Criterion) {
let segment = black_box(CubicSegment::new_bezier_easing(
vec2(0.25, 0.1),
vec2(0.25, 1.0),
));
c.bench_function(bench!("segment_ease"), |b| {
let mut t = 0;
b.iter_batched(
|| {
// Increment `t` by 1, but use modulo to constrain it to `0..=1000`.
t = (t + 1) % 1001;
// Return time as a decimal between 0 and 1, inclusive.
t as f32 / 1000.0
},
|t| segment.ease(t),
BatchSize::SmallInput,
);
});
}
fn curve_position(c: &mut Criterion) {
/// A helper function that benchmarks calling [`CubicCurve::position()`] over a generic [`VectorSpace`].
fn bench_curve<M: Measurement, P: VectorSpace>(
group: &mut BenchmarkGroup<M>,
name: &str,
curve: CubicCurve<P>,
) {
group.bench_with_input(BenchmarkId::from_parameter(name), &curve, |b, curve| {
b.iter(|| curve.position(black_box(0.5)));
});
}
let mut group = c.benchmark_group(bench!("curve_position"));
let bezier_2 = CubicBezier::new([[
vec2(0.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 0.0),
vec2(1.0, 1.0),
]])
.to_curve()
.unwrap();
bench_curve(&mut group, "vec2", bezier_2);
let bezier_3 = CubicBezier::new([[
vec3(0.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(1.0, 0.0, 0.0),
vec3(1.0, 1.0, 1.0),
]])
.to_curve()
.unwrap();
bench_curve(&mut group, "vec3", bezier_3);
let bezier_3a = CubicBezier::new([[
vec3a(0.0, 0.0, 0.0),
vec3a(0.0, 1.0, 0.0),
vec3a(1.0, 0.0, 0.0),
vec3a(1.0, 1.0, 1.0),
]])
.to_curve()
.unwrap();
bench_curve(&mut group, "vec3a", bezier_3a);
group.finish();
}
fn curve_iter_positions(c: &mut Criterion) {
let bezier = CubicBezier::new([[
vec3a(0.0, 0.0, 0.0),
vec3a(0.0, 1.0, 0.0),
vec3a(1.0, 0.0, 0.0),
vec3a(1.0, 1.0, 1.0),
]])
.to_curve()
.unwrap();
c.bench_function(bench!("curve_iter_positions"), |b| {
b.iter(|| {
for x in bezier.iter_positions(black_box(100)) {
// Discard `x`, since we just care about `iter_positions()` being consumed, but make
// the compiler believe `x` is being used so it doesn't eliminate the iterator.
black_box(x);
}
});
});
}