Skip to content

Commit 2ea0061

Browse files
committed
Add generic cubic splines to bevy_math (#7683)
# Objective - Make cubic splines more flexible and more performant - Remove the existing spline implementation that is generic over many degrees - This is a potential performance footgun and adds type complexity for negligible gain. - Add implementations of: - Bezier splines - Cardinal splines (inc. Catmull-Rom) - B-Splines - Hermite splines https://user-images.githubusercontent.com/2632925/221780519-495d1b20-ab46-45b4-92a3-32c46da66034.mp4 https://user-images.githubusercontent.com/2632925/221780524-2b154016-699f-404f-9c18-02092f589b04.mp4 https://user-images.githubusercontent.com/2632925/221780525-f934f99d-9ad4-4999-bae2-75d675f5644f.mp4 ## Solution - Implements the concept that splines are curve generators (e.g. https://youtu.be/jvPPXbo87ds?t=3488) via the `CubicGenerator` trait. - Common splines are bespoke data types that implement this trait. This gives us flexibility to add custom spline-specific methods on these types, while ultimately all generating a `CubicCurve`. - All splines generate `CubicCurve`s, which are a chain of precomputed polynomial coefficients. This means that all splines have the same evaluation cost, as the calculations for determining position, velocity, and acceleration are all identical. In addition, `CubicCurve`s are simply a list of `CubicSegment`s, which are evaluated from t=0 to t=1. This also means cubic splines of different type can be chained together, as ultimately they all are simply a collection of `CubicSegment`s. - Because easing is an operation on a singe segment of a Bezier curve, we can simply implement easing on `Beziers` that use the `Vec2` type for points. Higher level crates such as `bevy_ui` can wrap this in a more ergonomic interface as needed. ### Performance Measured on a desktop i5 8600K (6-year-old CPU): - easing: 2.7x faster (19ns) - cubic vec2 position sample: 1.5x faster (1.8ns) - cubic vec3 position sample: 1.5x faster (2.6ns) - cubic vec3a position sample: 1.9x faster (1.4ns) On a laptop i7 11800H: - easing: 16ns - cubic vec2 position sample: 1.6ns - cubic vec3 position sample: 2.3ns - cubic vec3a position sample: 1.2ns --- ## Changelog - Added a generic cubic curve trait, and implementation for Cardinal splines (including Catmull-Rom), B-Splines, Beziers, and Hermite Splines. 2D cubic curve segments also implement easing functionality for animation.
1 parent 47ddd4a commit 2ea0061

File tree

4 files changed

+650
-548
lines changed

4 files changed

+650
-548
lines changed

benches/benches/bevy_math/bezier.rs

Lines changed: 44 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,89 @@
11
use criterion::{black_box, criterion_group, criterion_main, Criterion};
22

3-
use bevy_math::*;
3+
use bevy_math::{prelude::*, *};
44

55
fn easing(c: &mut Criterion) {
6-
let cubic_bezier = CubicBezierEasing::new(vec2(0.25, 0.1), vec2(0.25, 1.0));
6+
let cubic_bezier = CubicSegment::new_bezier(vec2(0.25, 0.1), vec2(0.25, 1.0));
77
c.bench_function("easing_1000", |b| {
88
b.iter(|| {
99
(0..1000).map(|i| i as f32 / 1000.0).for_each(|t| {
10-
cubic_bezier.ease(black_box(t));
10+
black_box(cubic_bezier.ease(black_box(t)));
1111
})
1212
});
1313
});
1414
}
1515

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-
6116
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]]);
17+
let bezier = Bezier::new([[
18+
vec2(0.0, 0.0),
19+
vec2(0.0, 1.0),
20+
vec2(1.0, 0.0),
21+
vec2(1.0, 1.0),
22+
]])
23+
.to_curve();
6324
c.bench_function("cubic_position_Vec2", |b| {
64-
b.iter(|| bezier.position(black_box(0.5)));
25+
b.iter(|| black_box(bezier.position(black_box(0.5))));
6526
});
6627
}
6728

6829
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-
]);
30+
let bezier = Bezier::new([[
31+
vec3a(0.0, 0.0, 0.0),
32+
vec3a(0.0, 1.0, 0.0),
33+
vec3a(1.0, 0.0, 0.0),
34+
vec3a(1.0, 1.0, 1.0),
35+
]])
36+
.to_curve();
7537
c.bench_function("cubic_position_Vec3A", |b| {
76-
b.iter(|| bezier.position(black_box(0.5)));
38+
b.iter(|| black_box(bezier.position(black_box(0.5))));
7739
});
7840
}
7941

8042
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-
]);
43+
let bezier = Bezier::new([[
44+
vec3(0.0, 0.0, 0.0),
45+
vec3(0.0, 1.0, 0.0),
46+
vec3(1.0, 0.0, 0.0),
47+
vec3(1.0, 1.0, 1.0),
48+
]])
49+
.to_curve();
8750
c.bench_function("cubic_position_Vec3", |b| {
88-
b.iter(|| bezier.position(black_box(0.5)));
51+
b.iter(|| black_box(bezier.position(black_box(0.5))));
8952
});
9053
}
9154

9255
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-
]);
56+
let bezier = Bezier::new([[
57+
vec3a(0.0, 0.0, 0.0),
58+
vec3a(0.0, 1.0, 0.0),
59+
vec3a(1.0, 0.0, 0.0),
60+
vec3a(1.0, 1.0, 1.0),
61+
]])
62+
.to_curve();
9963
c.bench_function("build_pos_cubic_100_points", |b| {
100-
b.iter(|| bezier.iter_positions(black_box(100)).collect::<Vec<_>>());
64+
b.iter(|| black_box(bezier.iter_positions(black_box(100)).collect::<Vec<_>>()));
10165
});
10266
}
10367

10468
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-
]);
69+
let bezier = Bezier::new([[
70+
vec3a(0.0, 0.0, 0.0),
71+
vec3a(0.0, 1.0, 0.0),
72+
vec3a(1.0, 0.0, 0.0),
73+
vec3a(1.0, 1.0, 1.0),
74+
]])
75+
.to_curve();
11176
c.bench_function("build_accel_cubic_100_points", |b| {
112-
b.iter(|| bezier.iter_positions(black_box(100)).collect::<Vec<_>>());
77+
b.iter(|| black_box(bezier.iter_positions(black_box(100)).collect::<Vec<_>>()));
11378
});
11479
}
11580

11681
criterion_group!(
11782
benches,
11883
easing,
119-
fifteen_degree,
120-
quadratic_2d,
121-
quadratic,
122-
quadratic_vec3,
12384
cubic_2d,
124-
cubic,
12585
cubic_vec3,
86+
cubic,
12687
build_pos_cubic,
12788
build_accel_cubic,
12889
);

0 commit comments

Comments
 (0)