From 81b39464c009a165f83118c5ab793769f7d711a3 Mon Sep 17 00:00:00 2001 From: Matty Date: Fri, 11 Oct 2024 16:52:58 -0400 Subject: [PATCH] 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. --- crates/bevy_animation/src/graph.rs | 38 ++++++++++++++++++++---------- crates/bevy_animation/src/lib.rs | 16 ++++++++++++- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs index 45144aa4cd1cd..da910c93b5420 100644 --- a/crates/bevy_animation/src/graph.rs +++ b/crates/bevy_animation/src/graph.rs @@ -148,8 +148,9 @@ pub type AnimationDiGraph = DiGraph; /// The index of either an animation or blend node in the animation graph. /// -/// These indices are the way that [`crate::AnimationPlayer`]s identify -/// particular animations. +/// These indices are the way that [animation players] identify each animation. +/// +/// [animation players]: crate::AnimationPlayer pub type AnimationNodeIndex = NodeIndex; /// An individual node within an animation graph. @@ -157,9 +158,7 @@ pub type AnimationNodeIndex = NodeIndex; /// 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 the graph, contain animation clips to play. Blend and add nodes describe -/// how to combine their children to produce a final animation. The difference -/// between blend nodes and add nodes is that blend nodes normalize the weights -/// of their children to 1.0, while add nodes don't. +/// how to combine their children to produce a final animation. #[derive(Clone, Reflect, Debug)] pub struct AnimationGraphNode { /// 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. pub mask: AnimationMask, - /// The weight of this node. + /// The weight of this node, which signifies its contribution in blending. + /// + /// Note that this does not propagate down the graph hierarchy; rather, + /// each [Blend] and [Add] node uses the weights of its children to determine + /// 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. /// - /// Weights are propagated down to descendants. Thus if an animation clip - /// has weight 0.3 and its parent blend node has effective weight 0.6, the - /// computed weight of the animation clip is 0.18. + /// [Blend]: AnimationNodeType::Blend + /// [Add]: AnimationNodeType::Add + /// [active animation weight]: crate::ActiveAnimation::weight pub weight: f32, } @@ -201,11 +213,13 @@ pub enum AnimationNodeType { #[default] Blend, - /// An *additive blend node*, which combines the animations of its children, - /// scaled by their weights. + /// An *additive blend node*, which combines the animations of its children + /// additively. /// /// 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 /// portion of a rig on top of the main animation. For example, an add node diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index c1b8f36bba3aa..fa86e82b9e415 100755 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -84,7 +84,7 @@ use crate::{ /// [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); -/// 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 #[derive(Debug, TypePath)] @@ -422,6 +422,20 @@ impl AnimationClip { /// If the curve extends beyond the current duration of this clip, this /// method lengthens this clip to include the entire time span that the /// 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( &mut self, target_id: AnimationTargetId,