Some animation doc improvements (#15860)

# Objective

Animation docs could use some clarification regarding:
- how exactly curves are evaluated
- how additive blend nodes actually work

## Solution

Add some documentation that explains how curve domains are used and how
additive blend nodes treat their children.

## Commentary

The way additive blend nodes work right now is a little bit weird, since
their first child's weight is ignored. Arguably this makes sense, since
additive animations are authored differently from ordinary animations,
but it also feels a bit strange. We could make the first node's weight
actually be applied, and the present behavior would be recovered when
the weight is set to 1.

The main disadvantage of how things are set up now is that combining a
bunch of additive animations without a base pose is pretty awkward (e.g.
to add them onto a base pose later in the graph). If we changed it, the
main downside would be that reusing the same animation on different
parts of the graph is harder; on the other hand, the weights can be
locally reassigned by using blend nodes with no other children, which
rectifies this shortfall.
This commit is contained in:
Matty 2024-10-11 16:52:58 -04:00 committed by GitHub
parent 60a9a81602
commit 81b39464c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 41 additions and 13 deletions

View file

@ -148,8 +148,9 @@ pub type AnimationDiGraph = DiGraph<AnimationGraphNode, (), u32>;
/// The index of either an animation or blend node in the animation graph. /// The index of either an animation or blend node in the animation graph.
/// ///
/// These indices are the way that [`crate::AnimationPlayer`]s identify /// These indices are the way that [animation players] identify each animation.
/// particular animations. ///
/// [animation players]: crate::AnimationPlayer
pub type AnimationNodeIndex = NodeIndex<u32>; pub type AnimationNodeIndex = NodeIndex<u32>;
/// An individual node within an animation graph. /// An individual node within an animation graph.
@ -157,9 +158,7 @@ pub type AnimationNodeIndex = NodeIndex<u32>;
/// The [`AnimationGraphNode::node_type`] field specifies the type of node: one /// The [`AnimationGraphNode::node_type`] field specifies the type of node: one
/// of a *clip node*, a *blend node*, or an *add node*. Clip nodes, the leaves /// of a *clip node*, a *blend node*, or an *add node*. Clip nodes, the leaves
/// of the graph, contain animation clips to play. Blend and add nodes describe /// of the graph, contain animation clips to play. Blend and add nodes describe
/// how to combine their children to produce a final animation. The difference /// how to combine their children to produce a final animation.
/// between blend nodes and add nodes is that blend nodes normalize the weights
/// of their children to 1.0, while add nodes don't.
#[derive(Clone, Reflect, Debug)] #[derive(Clone, Reflect, Debug)]
pub struct AnimationGraphNode { pub struct AnimationGraphNode {
/// Animation node data specific to the type of node (clip, blend, or add). /// Animation node data specific to the type of node (clip, blend, or add).
@ -176,11 +175,24 @@ pub struct AnimationGraphNode {
/// this node and its descendants *cannot* animate mask group N. /// this node and its descendants *cannot* animate mask group N.
pub mask: AnimationMask, pub mask: AnimationMask,
/// The weight of this node. /// The weight of this node, which signifies its contribution in blending.
/// ///
/// Weights are propagated down to descendants. Thus if an animation clip /// Note that this does not propagate down the graph hierarchy; rather,
/// has weight 0.3 and its parent blend node has effective weight 0.6, the /// each [Blend] and [Add] node uses the weights of its children to determine
/// computed weight of the animation clip is 0.18. /// the total animation that is accumulated at that node. The parent node's
/// weight is used only to determine the contribution of that total animation
/// in *further* blending.
///
/// In other words, it is as if the blend node is replaced by a single clip
/// node consisting of the blended animation with the weight specified at the
/// blend node.
///
/// For animation clips, this weight is also multiplied by the [active animation weight]
/// before being applied.
///
/// [Blend]: AnimationNodeType::Blend
/// [Add]: AnimationNodeType::Add
/// [active animation weight]: crate::ActiveAnimation::weight
pub weight: f32, pub weight: f32,
} }
@ -201,11 +213,13 @@ pub enum AnimationNodeType {
#[default] #[default]
Blend, Blend,
/// An *additive blend node*, which combines the animations of its children, /// An *additive blend node*, which combines the animations of its children
/// scaled by their weights. /// additively.
/// ///
/// The weights of all the children of this node are *not* normalized to /// The weights of all the children of this node are *not* normalized to
/// 1.0. /// 1.0. Rather, the first child is used as a base, ignoring its weight,
/// while the others are multiplied by their respective weights and then
/// added in sequence to the base.
/// ///
/// Add nodes are primarily useful for superimposing an animation for a /// Add nodes are primarily useful for superimposing an animation for a
/// portion of a rig on top of the main animation. For example, an add node /// portion of a rig on top of the main animation. For example, an add node

View file

@ -84,7 +84,7 @@ use crate::{
/// [UUID namespace]: https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based) /// [UUID namespace]: https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)
pub static ANIMATION_TARGET_NAMESPACE: Uuid = Uuid::from_u128(0x3179f519d9274ff2b5966fd077023911); pub static ANIMATION_TARGET_NAMESPACE: Uuid = Uuid::from_u128(0x3179f519d9274ff2b5966fd077023911);
/// Contains an [animation curve] which is used to animate entities. /// Contains an [animation curve] which is used to animate a property of an entity.
/// ///
/// [animation curve]: AnimationCurve /// [animation curve]: AnimationCurve
#[derive(Debug, TypePath)] #[derive(Debug, TypePath)]
@ -422,6 +422,20 @@ impl AnimationClip {
/// If the curve extends beyond the current duration of this clip, this /// If the curve extends beyond the current duration of this clip, this
/// method lengthens this clip to include the entire time span that the /// method lengthens this clip to include the entire time span that the
/// curve covers. /// curve covers.
///
/// More specifically:
/// - This clip will be sampled on the interval `[0, duration]`.
/// - Each curve in the clip is sampled by first clamping the sample time to its [domain].
/// - Curves that extend forever never contribute to the duration.
///
/// For example, a curve with domain `[2, 5]` will extend the clip to cover `[0, 5]`
/// when added and will produce the same output on the entire interval `[0, 2]` because
/// these time values all get clamped to `2`.
///
/// By contrast, a curve with domain `[-10, ∞]` will never extend the clip duration when
/// added and will be sampled only on `[0, duration]`, ignoring all negative time values.
///
/// [domain]: AnimationCurve::domain
pub fn add_curve_to_target( pub fn add_curve_to_target(
&mut self, &mut self,
target_id: AnimationTargetId, target_id: AnimationTargetId,