bevy/crates
Matty 9beb1d96e7
Incorporate all node weights in additive blending (#16279)
# Objective

In the existing implementation, additive blending effectively treats the
node with least index specially by basically forcing its weight to be
`1.0` regardless of what its computed weight would be (based on the
weights in the `AnimationGraph` and `AnimationPlayer`).

Arguably this makes some amount of sense, because the "base" animation
is often one which was not authored to be used additively, meaning that
its sampled values are interpreted absolutely rather than as deltas.
However, this also leads to strange behavior with respect to animation
masks: if the "base" animation is masked out on some target, then the
next node is treated as the "base" animation, despite the fact that it
would normally be interpreted additively, and the weight of that
animation is thrown away as a result.

This is all kind of weird and revolves around special treatment (if the
behavior is even really intentional in the first place). From a
mathematical standpoint, there is nothing special about how the "base"
animation must be treated other than having a weight of 1.0 under an
`Add` node, which is something that the user can do without relying on
some bizarre corner-case behavior of the animation system — this is the
only present situation under which weights are discarded.

This PR changes this behavior so that the weight of every node is
incorporated. In other words, for an animation graph that looks like
this:
```text
┌───────────────┐                                 
│Base clip      ┼──┐                              
│      0.5      │  │                              
└───────────────┘  │                              
┌───────────────┐  │  ┌───────────────┐     ┌────┐
│Additive clip 1┼──┼─►┤Additive blend ┼────►│Root│
│      0.1      │  │  │      1.0      │     └────┘
└───────────────┘  │  └───────────────┘           
┌───────────────┐  │                              
│Additive clip 2┼──┘                              
│      0.2      │                                 
└───────────────┘                                 
```

Previously, the result would have been
```text
base_clip + 0.1 * additive_clip_1 + 0.2 * additive_clip_2
```

whereas now it would be
```text
0.5 * base_clip + 0.1 * additive_clip_1 + 0.2 * additive_clip_2
```

and in the scenario where `base_clip` is masked out:
```text
additive_clip_1 + 0.2 * additive_clip_2
```
vs.
```text
0.1 * additive_clip_1 + 0.2 * additive_clip_2
```

## Solution

For background, the way that the additive blending procedure works is
something like this:
- During graph traversal, the node values and weights of the children
are pushed onto the evaluator `stack`. The traversal order guarantees
that the item with least node index will be on top.
- Once we reach the `Add` node itself, we start popping off the `stack`
and into the evaluator's `blend_register`, which is an accumulator
holding up to one weight-value pair:
- If the `blend_register` is empty, it is filled using data from the top
of the `stack`.
- Otherwise, the `blend_register` is combined with data popped from the
`stack` and updated.

In the example above, the additive blending steps would look like this
(with the pre-existing implementation):
1. The `blend_register` is empty, so we pop `(base_clip, 0.5)` from the
top of the `stack` and put it in. Now the value of the `blend_register`
is `(base_clip, 0.5)`.
2. The `blend_register` is non-empty: we pop `(additive_clip_1, 0.1)`
from the top of the `stack` and combine it additively with the value in
the `blend_register`, forming `(base_clip + 0.1 * additive_clip_1, 0.6)`
in the `blend_register` (the carried weight value goes unused).
3. The `blend_register` is non-empty: we pop `(additive_clip_2, 0.2)`
from the top of the `stack` and combine it additively with the value in
the `blend_register`, forming `(base_clip + 0.1 * additive_clip_1 + 0.2
* additive_clip_2, 0.8)` in the `blend_register`.

The solution in this PR changes step 1: the `base_clip` is multiplied by
its weight as it is added to the `blend_register` in the first place,
yielding `0.5 * base_clip + 0.1 * additive_clip_1 + 0.2 *
additive_clip_2` as the final result.

### Note for reviewers

It might be tempting to look at the code, which contains a segment that
looks like this:
```rust
if additive {
    current_value = A::blend(
        [
            BlendInput {
                weight: 1.0, // <--
                value: current_value,
                additive: true,
            },
            BlendInput {
                weight: weight_to_blend,
                value: value_to_blend,
                additive: true,
            },
        ]
        .into_iter(),
    );
} 
```
and conclude that the explicit value of `1.0` is responsible for
overwriting the weight of the base animation. This is incorrect.

Rather, this additive blend has to be written this way because it is
multiplying the *existing value in the blend register* by 1 (i.e. not
doing anything) before adding the next value to it. Changing this to
another quantity (e.g. the existing weight) would cause the value in the
blend register to be spuriously multiplied down.

## Testing

Tested on `animation_masks` example. Checked `morph_weights` example as
well.

## Migration Guide

I will write a migration guide later if this change is not included in
0.15.
2024-11-07 19:12:08 +00:00
..
bevy_a11y Bump accesskit and accesskit_winit. (#16234) 2024-11-04 20:07:38 +00:00
bevy_animation Incorporate all node weights in additive blending (#16279) 2024-11-07 19:12:08 +00:00
bevy_app Improve SubApp documentation example (#16160) 2024-10-30 22:12:25 +00:00
bevy_asset Support creating asset directories (#16220) 2024-11-04 22:06:00 +00:00
bevy_audio Fix audio not playing (#15638) 2024-10-04 01:07:09 +00:00
bevy_color Upgrade to wgpu 23 (#15988) 2024-11-05 21:18:48 +00:00
bevy_core chore(deps): remove unused uuid dependency from bevy_core (#16253) 2024-11-05 23:31:58 +00:00
bevy_core_pipeline Upgrade to wgpu 23 (#15988) 2024-11-05 21:18:48 +00:00
bevy_derive move ANDROID_APP to bevy_window (#15585) 2024-10-02 03:01:06 +00:00
bevy_dev_tools fix bevy_dev_tools build (#16099) 2024-10-25 20:14:39 +00:00
bevy_diagnostic Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_dylib Generate links to definition in source code pages on docs.rs and dev-docs.bevyengine.org (#12965) 2024-07-29 23:10:16 +00:00
bevy_ecs Make ComponentTicks field public (#16269) 2024-11-06 22:21:04 +00:00
bevy_encase_derive Update `glam to 0.29, encase` to 0.10. (#15249) 2024-09-23 19:44:02 +00:00
bevy_gilrs Use Name component for gamepad (#16233) 2024-11-05 00:30:48 +00:00
bevy_gizmos Fix gizmos (#15836) 2024-10-10 22:04:04 +00:00
bevy_gltf Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_hierarchy fix: add reflect to SceneInstanceReady and other observers/events (#16018) 2024-10-20 13:51:41 +00:00
bevy_image Upgrade to wgpu 23 (#15988) 2024-11-05 21:18:48 +00:00
bevy_input Use Name component for gamepad (#16233) 2024-11-05 00:30:48 +00:00
bevy_internal Fix bevy_picking plugin suffixes (#16082) 2024-10-25 20:11:51 +00:00
bevy_log Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_macro_utils Modify derive_label to support no_std environments (#15465) 2024-09-27 20:23:26 +00:00
bevy_math Bump crate-ci/typos from 1.26.8 to 1.27.0 (#16236) 2024-11-05 01:33:27 +00:00
bevy_mesh Upgrade to wgpu 23 (#15988) 2024-11-05 21:18:48 +00:00
bevy_mikktspace Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_pbr Fix WGSL formatting inconsistency on mesh_view_binding (#16202) 2024-11-04 15:32:38 +00:00
bevy_picking Expose picking pointer state as a resource (#16229) 2024-11-04 22:06:14 +00:00
bevy_ptr Reduce compile time of bevy_ptr::OwnedPtr::make function (#15644) 2024-10-28 21:15:00 +00:00
bevy_reflect Upgrade to wgpu 23 (#15988) 2024-11-05 21:18:48 +00:00
bevy_remote BRP System Ordering (#16198) 2024-11-05 21:05:11 +00:00
bevy_render Make BinnedRenderPhase fields for accessing batchable and unbatchable entities public (#16142) 2024-11-07 18:03:47 +00:00
bevy_scene fix: add reflect to SceneInstanceReady and other observers/events (#16018) 2024-10-20 13:51:41 +00:00
bevy_sprite Improved UiImage and Sprite scaling and slicing APIs (#16088) 2024-11-04 15:14:03 +00:00
bevy_state Fix typo in bevy_ecs (#16195) 2024-10-31 19:20:01 +00:00
bevy_tasks Resolve unused_qualifications warnings (#16001) 2024-10-19 16:59:58 +00:00
bevy_text Use CosmicFontSystem in public bevy_text APIs and remove cosmic_text re-export (#16063) 2024-10-23 20:05:28 +00:00
bevy_time Use en-us locale for typos (#16037) 2024-10-20 18:55:17 +00:00
bevy_transform Improved the global transform api to access rotation and scale (#16211) 2024-11-04 15:35:16 +00:00
bevy_ui Require ContentSize for UiImage (#16262) 2024-11-06 14:56:28 +00:00
bevy_utils More #[doc(fake_variadic)] goodness (#16108) 2024-10-27 19:01:50 +00:00
bevy_window Support prefers_home_indicator_hidden (#16005) 2024-10-31 16:09:30 +00:00
bevy_winit properly flag using CustomCursor::Url in wasm (#16255) 2024-11-06 13:14:12 +00:00