Skip to content

Commit

Permalink
Add Beziers to bevy_math (#7653)
Browse files Browse the repository at this point in the history
# Objective

- Adds foundational math for Bezier curves, useful for UI/2D/3D animation and smooth paths.

https://user-images.githubusercontent.com/2632925/218883143-e138f994-1795-40da-8c59-21d779666991.mp4

## Solution

- Adds the generic `Bezier` type, and a `Point` trait. The `Point` trait allows us to use control points of any dimension, as long as they support vector math. I've implemented it for `f32`(1D), `Vec2`(2D), and `Vec3`/`Vec3A`(3D).
- Adds `CubicBezierEasing` on top of `Bezier` with the addition of an implementation of cubic Bezier easing, which is a foundational tool for UI animation.
  - This involves solving for $t$ in the parametric Bezier function $B(t)$ using the Newton-Raphson method to find a value with error $\leq$ 1e-7, capped at 8 iterations.
- Added type aliases for common Bezier curves: `CubicBezier2d`, `CubicBezier3d`, `QuadraticBezier2d`, and `QuadraticBezier3d`. These types use `Vec3A` to represent control points, as this was found to have an 80-90% speedup over using `Vec3`.
- Benchmarking shows quadratic/cubic Bezier evaluations $B(t)$ take \~1.8/2.4ns respectively. Easing, which requires an iterative solve takes \~50ns for cubic Beziers. 

---

## Changelog

- Added `CubicBezier2d`, `CubicBezier3d`, `QuadraticBezier2d`, and `QuadraticBezier3d` types with methods for sampling position, velocity, and acceleration. The generic `Bezier` type is also available, and generic over any degree of Bezier curve.
- Added `CubicBezierEasing`, with additional methods to allow for smooth easing animations.
  • Loading branch information
aevyrie committed Feb 20, 2023
1 parent 0c98f9a commit 2a598d3
Show file tree
Hide file tree
Showing 4 changed files with 600 additions and 2 deletions.
6 changes: 6 additions & 0 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ bevy_ecs = { path = "../crates/bevy_ecs" }
bevy_reflect = { path = "../crates/bevy_reflect" }
bevy_tasks = { path = "../crates/bevy_tasks" }
bevy_utils = { path = "../crates/bevy_utils" }
bevy_math = { path = "../crates/bevy_math" }

[profile.release]
opt-level = 3
Expand Down Expand Up @@ -50,3 +51,8 @@ harness = false
name = "iter"
path = "benches/bevy_tasks/iter.rs"
harness = false

[[bench]]
name = "bezier"
path = "benches/bevy_math/bezier.rs"
harness = false
129 changes: 129 additions & 0 deletions benches/benches/bevy_math/bezier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};

use bevy_math::*;

fn easing(c: &mut Criterion) {
let cubic_bezier = CubicBezierEasing::new(vec2(0.25, 0.1), vec2(0.25, 1.0));
c.bench_function("easing_1000", |b| {
b.iter(|| {
(0..1000).map(|i| i as f32 / 1000.0).for_each(|t| {
cubic_bezier.ease(black_box(t));
})
});
});
}

fn fifteen_degree(c: &mut Criterion) {
let bezier = Bezier::<Vec3A, 16>::new([
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
]);
c.bench_function("fifteen_degree_position", |b| {
b.iter(|| bezier.position(black_box(0.5)));
});
}

fn quadratic_2d(c: &mut Criterion) {
let bezier = QuadraticBezier2d::new([[0.0, 0.0], [0.0, 1.0], [1.0, 1.0]]);
c.bench_function("quadratic_position_Vec2", |b| {
b.iter(|| bezier.position(black_box(0.5)));
});
}

fn quadratic(c: &mut Criterion) {
let bezier = QuadraticBezier3d::new([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 1.0]]);
c.bench_function("quadratic_position_Vec3A", |b| {
b.iter(|| bezier.position(black_box(0.5)));
});
}

fn quadratic_vec3(c: &mut Criterion) {
let bezier = Bezier::<Vec3, 3>::new([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 1.0]]);
c.bench_function("quadratic_position_Vec3", |b| {
b.iter(|| bezier.position(black_box(0.5)));
});
}

fn cubic_2d(c: &mut Criterion) {
let bezier = CubicBezier2d::new([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]]);
c.bench_function("cubic_position_Vec2", |b| {
b.iter(|| bezier.position(black_box(0.5)));
});
}

fn cubic(c: &mut Criterion) {
let bezier = CubicBezier3d::new([
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
]);
c.bench_function("cubic_position_Vec3A", |b| {
b.iter(|| bezier.position(black_box(0.5)));
});
}

fn cubic_vec3(c: &mut Criterion) {
let bezier = Bezier::<Vec3, 4>::new([
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
]);
c.bench_function("cubic_position_Vec3", |b| {
b.iter(|| bezier.position(black_box(0.5)));
});
}

fn build_pos_cubic(c: &mut Criterion) {
let bezier = CubicBezier3d::new([
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
]);
c.bench_function("build_pos_cubic_100_points", |b| {
b.iter(|| bezier.iter_positions(black_box(100)).collect::<Vec<_>>());
});
}

fn build_accel_cubic(c: &mut Criterion) {
let bezier = CubicBezier3d::new([
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 1.0],
]);
c.bench_function("build_accel_cubic_100_points", |b| {
b.iter(|| bezier.iter_positions(black_box(100)).collect::<Vec<_>>());
});
}

criterion_group!(
benches,
easing,
fifteen_degree,
quadratic_2d,
quadratic,
quadratic_vec3,
cubic_2d,
cubic,
cubic_vec3,
build_pos_cubic,
build_accel_cubic,
);
criterion_main!(benches);
Loading

0 comments on commit 2a598d3

Please sign in to comment.