Skip to content

Commit a5296e3

Browse files
committed
Start bevyengine#49: Animation Primitives
1 parent f1ba2d8 commit a5296e3

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed

rfcs/48-animation-primitives.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Feature Name: `animation-primitives`
2+
3+
## Summary
4+
5+
Animation is a particularly complex, with many stateful and intersecting
6+
elements. This RFC aims to detail a set of lowest-level APIs for authoring and
7+
playing animations within Bevy.
8+
9+
## Motivation
10+
11+
Animation is at the heart of modern game development. A game engine without an
12+
animation system is generally considered not yet production ready.
13+
14+
This RFC aims to detail the absolute lowest level APIs to unblock ecosystem
15+
level experimentation with building more complex animation systems (i.e. inverse
16+
kinematics, animation state machine, humanoid retargetting, etc.)
17+
18+
## Scope
19+
20+
Animation is a huge area that spans multiple problem domains:
21+
22+
1. **Storage**: this generally covers on-disk storage in the form of assets as
23+
well as the in-memory representation of static animation data.
24+
2. **Sampling**: this is how we sample the animation assets over time and
25+
transform it into what is applied to the animated entities.
26+
3. **Application**: this is how we applied the sampled values to animated
27+
entities.
28+
4. **Composition**: this is how we compose simple clips to make more complex
29+
animated behaviors.
30+
4. **Authoring**: this is how we create the animation assets used by the engine.
31+
32+
This RFC specifically aims to resolve only problems within the domains of storage
33+
and sampling. Application can be distinctly decoupled from these earlier two
34+
stages, treating the sampled values as a black box output, and composition and
35+
authoring can be built separately upon the primitives provided by this RFC and
36+
thus are explicit non-goals here.
37+
38+
## User-facing explanation
39+
40+
The core of this system is a trait called `Sample<T>` which allows sampling
41+
values of `T` from an underlying type at a provided time. `T` here can be
42+
anything considered animatable. A few examples of high priority types to be
43+
supported here are:
44+
45+
- `f32`/`f64`
46+
- `Vec2`/`Vec3`/`Vec3A`/`Vec4` (and their `f64` variants)
47+
- `Color`
48+
- `Option<T>` where `T` can also be sampled
49+
- `bool` for toggling on and off features.
50+
- `Range<T>` for a range for randomly sampling from (think particle systems)
51+
- `Handle<T>` for sprite animation, though can be generically used for any asset
52+
swapping.
53+
- etc.
54+
55+
Built on top of this trait is the concept of a **animation graph**, a runtime
56+
mutable directed acyclic graph of nodes for altering the sampling behavior for
57+
a given property. There is always one root level node that is directly sampled
58+
from the app world. It can either be a terminal node, or be a composition node
59+
that samples it's children for values and combines the outputs before passing it
60+
upstream. Some examples include:
61+
62+
- `MixerNode<T>` - a stateful multi-input composition node that outputs a
63+
weighted sum of it's inputs. Can be used to make arbitrary blending of it's
64+
inputs.
65+
- `SelectorNode<T>` - a multi-input composition node that outputs only the
66+
currently selected input as it's output. All other inputs are not evaluated.
67+
Like a MixerNode with a one-hot weight blend, but more computationally
68+
efficient.
69+
- `ConstantNode<T>` - only outputs a constant value all times.
70+
- `RepeatNode<T>` - a single input node that loops it's initial input over time.
71+
- `PingPongNode<T>` - a single input node that loops it's initial input over
72+
time, will
73+
- `Arc<dyn Sample<T>>`/`Box<dyn Sample<T>>` - Anything that can be sampled can
74+
be used as an input to the graph.
75+
76+
The final lowest level and the concrete implementors of Sample are implemetors
77+
of the trait `Curve<T>`. Curves are meant to store the raw data for time-sampled
78+
values. There may be multiple implementations of this trait, and they're
79+
typically what is serialized and stored in assets.
80+
81+
Finally the last major introduction is the `AnimationClip` asset, which bundles a
82+
set of curves to the asoociated `Reflect` path they're bound to. This is the main
83+
metadata source for actually binding sampled outputs to the fields they're
84+
animating.
85+
86+
## Implementation strategy
87+
88+
Protoytpe implementation: https://github.com/HouraiTeahouse/bevy_prototype_animation
89+
90+
TODO: Complete this section.
91+
92+
## Drawbacks
93+
94+
The main drawback to this approach is code complexity. There are a lot of `dyn
95+
Trait` or `impl Trait` in these APIs and it might get a bit difficult to follow
96+
without external 1000ft views of the entire system. However, this is meant to be
97+
a flexible low-level API so this might be easier to gloss over once a more higher
98+
level solution built on top of it is made.
99+
100+
## Rationale and alternatives
101+
102+
Bevy absolutely needs some form of a first-party animation system, no modern game
103+
engine can be called production ready without one. Having this exist solely as a
104+
third-party ecosystem crate is definitely unacceptable as it would promote a
105+
facturing of the ecosystem with multiple incompatible baseline animation system
106+
implementations.
107+
108+
The design chosen here was explicitly to allow for maximum flexibility for both
109+
engine and game developers alike. The main alternative is to completely remove
110+
`Sample<T>`, the animation graph, and it's nodes, and let developers directly
111+
hook up animation curves from clips to entities. However, this lacks flexibility
112+
and does not allow for users of the API to inject their own alterations to the
113+
animation stream.
114+
115+
The main potential issue with the current implementation is the very heavy use of
116+
`Arc<dyn Sample<T>>` which has CPU cache, lifetime, and performance implications
117+
on the stored data. Atomic operations disrupt the CPU cache; however, they're
118+
only used when cloning or dropping an `Arc`. The structure of the animation
119+
graphs are, for the most part, static. Likewise, the trait object use is likely
120+
unavoidable so long as we rely on traits as a point of abstraction within the
121+
graph. An alternative mgiht be to we want to transfer ownership of the curve, and
122+
just make multiple copies of a potentially large and immutable animation data
123+
buffer, but that comes with a signfigant memory and CPU cache performance cost.
124+
125+
## Prior art
126+
127+
This proposal is largely inspired by Unity's [Playable][playable] API, which has
128+
a similar goal of building composable time-sequenced graphs for animation, audio,
129+
and game logic. Several other game engines have very similar APIs and features:
130+
131+
- Unreal has [AnimGraph][animgraph] for creating dynamic animations in
132+
Blueprints.
133+
- Godot has [animation trees][animation-trees] for creating dynamic animations in
134+
Blueprints.
135+
136+
The proposed API here doesn't purport or aim to directly replicate the features
137+
seen in these other engines, but provide the absolute bare minimum API so that
138+
engine developers or game developers can build them if they need to.
139+
140+
Currently nothing like this exists in the entire Rust ecosystem.
141+
142+
Note that while precedent set by other engines is some motivation, it does not on its own motivate an RFC.
143+
144+
[playable]: https://docs.unity3d.com/Manual/Playables.html
145+
[animgraph]: https://docs.unrealengine.com/4.27/en-US/AnimatingObjects/SkeletalMeshAnimation/AnimBlueprints/AnimGraph/
146+
[animation-trees]: https://docs.godotengine.org/en/stable/tutorials/animation/animation_tree.html
147+
148+
## Unresolved questions
149+
150+
TODO: Complete
151+
152+
## Future possibilities
153+
154+
TODO: Complete

0 commit comments

Comments
 (0)