Skip to content

Commit 2a598d3

Browse files
committed
Add Beziers to bevy_math (#7653)
# 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.
1 parent 0c98f9a commit 2a598d3

File tree

4 files changed

+600
-2
lines changed

4 files changed

+600
-2
lines changed

benches/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ bevy_ecs = { path = "../crates/bevy_ecs" }
1616
bevy_reflect = { path = "../crates/bevy_reflect" }
1717
bevy_tasks = { path = "../crates/bevy_tasks" }
1818
bevy_utils = { path = "../crates/bevy_utils" }
19+
bevy_math = { path = "../crates/bevy_math" }
1920

2021
[profile.release]
2122
opt-level = 3
@@ -50,3 +51,8 @@ harness = false
5051
name = "iter"
5152
path = "benches/bevy_tasks/iter.rs"
5253
harness = false
54+
55+
[[bench]]
56+
name = "bezier"
57+
path = "benches/bevy_math/bezier.rs"
58+
harness = false
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
2+
3+
use bevy_math::*;
4+
5+
fn easing(c: &mut Criterion) {
6+
let cubic_bezier = CubicBezierEasing::new(vec2(0.25, 0.1), vec2(0.25, 1.0));
7+
c.bench_function("easing_1000", |b| {
8+
b.iter(|| {
9+
(0..1000).map(|i| i as f32 / 1000.0).for_each(|t| {
10+
cubic_bezier.ease(black_box(t));
11+
})
12+
});
13+
});
14+
}
15+
16+
fn fifteen_degree(c: &mut Criterion) {
17+
let bezier = Bezier::<Vec3A, 16>::new([
18+
[0.0, 0.0, 0.0],
19+
[0.0, 1.0, 0.0],
20+
[1.0, 0.0, 0.0],
21+
[1.0, 1.0, 1.0],
22+
[0.0, 0.0, 0.0],
23+
[0.0, 1.0, 0.0],
24+
[1.0, 0.0, 0.0],
25+
[1.0, 1.0, 1.0],
26+
[0.0, 0.0, 0.0],
27+
[0.0, 1.0, 0.0],
28+
[1.0, 0.0, 0.0],
29+
[1.0, 1.0, 1.0],
30+
[0.0, 0.0, 0.0],
31+
[0.0, 1.0, 0.0],
32+
[1.0, 0.0, 0.0],
33+
[1.0, 1.0, 1.0],
34+
]);
35+
c.bench_function("fifteen_degree_position", |b| {
36+
b.iter(|| bezier.position(black_box(0.5)));
37+
});
38+
}
39+
40+
fn quadratic_2d(c: &mut Criterion) {
41+
let bezier = QuadraticBezier2d::new([[0.0, 0.0], [0.0, 1.0], [1.0, 1.0]]);
42+
c.bench_function("quadratic_position_Vec2", |b| {
43+
b.iter(|| bezier.position(black_box(0.5)));
44+
});
45+
}
46+
47+
fn quadratic(c: &mut Criterion) {
48+
let bezier = QuadraticBezier3d::new([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 1.0]]);
49+
c.bench_function("quadratic_position_Vec3A", |b| {
50+
b.iter(|| bezier.position(black_box(0.5)));
51+
});
52+
}
53+
54+
fn quadratic_vec3(c: &mut Criterion) {
55+
let bezier = Bezier::<Vec3, 3>::new([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 1.0]]);
56+
c.bench_function("quadratic_position_Vec3", |b| {
57+
b.iter(|| bezier.position(black_box(0.5)));
58+
});
59+
}
60+
61+
fn cubic_2d(c: &mut Criterion) {
62+
let bezier = CubicBezier2d::new([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]]);
63+
c.bench_function("cubic_position_Vec2", |b| {
64+
b.iter(|| bezier.position(black_box(0.5)));
65+
});
66+
}
67+
68+
fn cubic(c: &mut Criterion) {
69+
let bezier = CubicBezier3d::new([
70+
[0.0, 0.0, 0.0],
71+
[0.0, 1.0, 0.0],
72+
[1.0, 0.0, 0.0],
73+
[1.0, 1.0, 1.0],
74+
]);
75+
c.bench_function("cubic_position_Vec3A", |b| {
76+
b.iter(|| bezier.position(black_box(0.5)));
77+
});
78+
}
79+
80+
fn cubic_vec3(c: &mut Criterion) {
81+
let bezier = Bezier::<Vec3, 4>::new([
82+
[0.0, 0.0, 0.0],
83+
[0.0, 1.0, 0.0],
84+
[1.0, 0.0, 0.0],
85+
[1.0, 1.0, 1.0],
86+
]);
87+
c.bench_function("cubic_position_Vec3", |b| {
88+
b.iter(|| bezier.position(black_box(0.5)));
89+
});
90+
}
91+
92+
fn build_pos_cubic(c: &mut Criterion) {
93+
let bezier = CubicBezier3d::new([
94+
[0.0, 0.0, 0.0],
95+
[0.0, 1.0, 0.0],
96+
[1.0, 0.0, 0.0],
97+
[1.0, 1.0, 1.0],
98+
]);
99+
c.bench_function("build_pos_cubic_100_points", |b| {
100+
b.iter(|| bezier.iter_positions(black_box(100)).collect::<Vec<_>>());
101+
});
102+
}
103+
104+
fn build_accel_cubic(c: &mut Criterion) {
105+
let bezier = CubicBezier3d::new([
106+
[0.0, 0.0, 0.0],
107+
[0.0, 1.0, 0.0],
108+
[1.0, 0.0, 0.0],
109+
[1.0, 1.0, 1.0],
110+
]);
111+
c.bench_function("build_accel_cubic_100_points", |b| {
112+
b.iter(|| bezier.iter_positions(black_box(100)).collect::<Vec<_>>());
113+
});
114+
}
115+
116+
criterion_group!(
117+
benches,
118+
easing,
119+
fifteen_degree,
120+
quadratic_2d,
121+
quadratic,
122+
quadratic_vec3,
123+
cubic_2d,
124+
cubic,
125+
cubic_vec3,
126+
build_pos_cubic,
127+
build_accel_cubic,
128+
);
129+
criterion_main!(benches);

0 commit comments

Comments
 (0)