Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement minimal animation graphs in order to support animation blending. #11670

Closed
wants to merge 1 commit into from

Conversation

pcwalton
Copy link
Contributor

@pcwalton pcwalton commented Feb 3, 2024

Objective

This is a minimum viable implementation of bevyengine/rfcs#51. It allows Bevy to support animation blending.

Solution

This is just to start the conversation.

transforms: Query<&mut Transform>,
morph_weights: Query<&mut MorphWeights>,
parents: Query<(Has<AnimationPlayer>, Has<AnimationGraph>, Option<&Parent>)>,
mut animation_graphs: Query<(Entity, Option<&Parent>, &mut AnimationGraph)>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to drop the parallelization that is currently supported, and may be a significant performance regression for scenes with a large number of animated rigs.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason we cannot just use .par_iter_mut().for_each here? Seems like it would work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does look like par_iter would bring this in line with how things are currently dispatched in animation_player. Same exact query more or less.


let node = &mut self.animation_graph.graph[*node_index];
if let Some(ref mut playing_animation) = node.animation {
crate::apply_animation(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each time apply_animation is called, the query for each bone within the hierarchy is redone, which involves a hierarchy traversal and multiple string comparisons. This works, but the total overhead scales with the number of clips in the graph. This isn't a significant departure from what we currently have, so I don't want to block on this, but we need to address this sooner rather than later.

'mwq,
'mwa,
'mwb,
'mwc,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lifetimes definitely need to be addressed before we merge this. This is a little ridiculous.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these not share the same lifetime bound? See for example here and here.

Copy link
Contributor

@NthTensor NthTensor Feb 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to appease the borrow-checker with the following:

struct AnimationContext<'l, 'w, 's, 'a> {
    animation_graph: &'l mut AnimationGraph,
    time: &'l Time,
    root_entity: Entity,
    animation_clips: &'l Assets<AnimationClip>,
    parent: Option<&'w Parent>,
    parents: &'l Query<
        'w,
        's,
        (
            Has<AnimationPlayer>,
            Has<AnimationGraph>,
            Option<&'a Parent>,
        ),
    >,
    children: &'l Query<'w, 's, &'a Children>,
    names: &'l Query<'w, 's, &'a Name>,
    transforms: &'l Query<'w, 's, &'a mut Transform>,
    morph_weights: &'l Query<'w, 's, &'a mut MorphWeights>,
}

pub fn evaluate_animation_graphs<'a>(
    time: Res<Time>,
    animation_clips: Res<Assets<AnimationClip>>,
    children: Query<&'a Children>,
    names: Query<&'a Name>,
    transforms: Query<&'a mut Transform>,
    morph_weights: Query<&'a mut MorphWeights>,
    parents: Query<(
        Has<AnimationPlayer>,
        Has<AnimationGraph>,
        Option<&'a Parent>,
    )>,
    mut animation_graphs: Query<(Entity, Option<&'a Parent>, &'a mut AnimationGraph)>,
)

I don't think it is possible to reduce this further, and I'm not 100% confident this ends up the same as the original. Maybe 'a is actually static idk. Not currently sure what AnimationContext is for but I trust the process.

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-Animation Make things move and change over time labels Feb 3, 2024
@pcwalton
Copy link
Contributor Author

pcwalton commented Feb 4, 2024

I'm closing this as there will need to be a lot of changes.

@pcwalton pcwalton closed this Feb 4, 2024
github-merge-queue bot pushed a commit that referenced this pull request Feb 19, 2024
# Objective

Bevy's animation system currently does tree traversals based on `Name`
that aren't necessary. Not only do they require in unsafe code because
tree traversals are awkward with parallelism, but they are also somewhat
slow, brittle, and complex, which manifested itself as way too many
queries in #11670.

# Solution

Divide animation into two phases: animation *advancement* and animation
*evaluation*, which run after one another. *Advancement* operates on the
`AnimationPlayer` and sets the current animation time to match the game
time. *Evaluation* operates on all animation bones in the scene in
parallel and sets the transforms and/or morph weights based on the time
and the clip.

To do this, we introduce a new component, `AnimationTarget`, which the
asset loader places on every bone. It contains the ID of the entity
containing the `AnimationPlayer`, as well as a UUID that identifies
which bone in the animation the target corresponds to. In the case of
glTF, the UUID is derived from the full path name to the bone. The rule
that `AnimationTarget`s are descendants of the entity containing
`AnimationPlayer` is now just a convention, not a requirement; this
allows us to eliminate the unsafe code.

# Migration guide

* `AnimationClip` now uses UUIDs instead of hierarchical paths based on
the `Name` component to refer to bones. This has several consequences:
- A new component, `AnimationTarget`, should be placed on each bone that
you wish to animate, in order to specify its UUID and the associated
`AnimationPlayer`. The glTF loader automatically creates these
components as necessary, so most uses of glTF rigs shouldn't need to
change.
- Moving a bone around the tree, or renaming it, no longer prevents an
`AnimationPlayer` from affecting it.
- Dynamically changing the `AnimationPlayer` component will likely
require manual updating of the `AnimationTarget` components.
* Entities with `AnimationPlayer` components may now possess descendants
that also have `AnimationPlayer` components. They may not, however,
animate the same bones.
* As they aren't specific to `TypeId`s,
`bevy_reflect::utility::NoOpTypeIdHash` and
`bevy_reflect::utility::NoOpTypeIdHasher` have been renamed to
`bevy_reflect::utility::NoOpHash` and
`bevy_reflect::utility::NoOpHasher` respectively.
msvbg pushed a commit to msvbg/bevy that referenced this pull request Feb 26, 2024
# Objective

Bevy's animation system currently does tree traversals based on `Name`
that aren't necessary. Not only do they require in unsafe code because
tree traversals are awkward with parallelism, but they are also somewhat
slow, brittle, and complex, which manifested itself as way too many
queries in bevyengine#11670.

# Solution

Divide animation into two phases: animation *advancement* and animation
*evaluation*, which run after one another. *Advancement* operates on the
`AnimationPlayer` and sets the current animation time to match the game
time. *Evaluation* operates on all animation bones in the scene in
parallel and sets the transforms and/or morph weights based on the time
and the clip.

To do this, we introduce a new component, `AnimationTarget`, which the
asset loader places on every bone. It contains the ID of the entity
containing the `AnimationPlayer`, as well as a UUID that identifies
which bone in the animation the target corresponds to. In the case of
glTF, the UUID is derived from the full path name to the bone. The rule
that `AnimationTarget`s are descendants of the entity containing
`AnimationPlayer` is now just a convention, not a requirement; this
allows us to eliminate the unsafe code.

# Migration guide

* `AnimationClip` now uses UUIDs instead of hierarchical paths based on
the `Name` component to refer to bones. This has several consequences:
- A new component, `AnimationTarget`, should be placed on each bone that
you wish to animate, in order to specify its UUID and the associated
`AnimationPlayer`. The glTF loader automatically creates these
components as necessary, so most uses of glTF rigs shouldn't need to
change.
- Moving a bone around the tree, or renaming it, no longer prevents an
`AnimationPlayer` from affecting it.
- Dynamically changing the `AnimationPlayer` component will likely
require manual updating of the `AnimationTarget` components.
* Entities with `AnimationPlayer` components may now possess descendants
that also have `AnimationPlayer` components. They may not, however,
animate the same bones.
* As they aren't specific to `TypeId`s,
`bevy_reflect::utility::NoOpTypeIdHash` and
`bevy_reflect::utility::NoOpTypeIdHasher` have been renamed to
`bevy_reflect::utility::NoOpHash` and
`bevy_reflect::utility::NoOpHasher` respectively.
msvbg pushed a commit to msvbg/bevy that referenced this pull request Feb 26, 2024
# Objective

Bevy's animation system currently does tree traversals based on `Name`
that aren't necessary. Not only do they require in unsafe code because
tree traversals are awkward with parallelism, but they are also somewhat
slow, brittle, and complex, which manifested itself as way too many
queries in bevyengine#11670.

# Solution

Divide animation into two phases: animation *advancement* and animation
*evaluation*, which run after one another. *Advancement* operates on the
`AnimationPlayer` and sets the current animation time to match the game
time. *Evaluation* operates on all animation bones in the scene in
parallel and sets the transforms and/or morph weights based on the time
and the clip.

To do this, we introduce a new component, `AnimationTarget`, which the
asset loader places on every bone. It contains the ID of the entity
containing the `AnimationPlayer`, as well as a UUID that identifies
which bone in the animation the target corresponds to. In the case of
glTF, the UUID is derived from the full path name to the bone. The rule
that `AnimationTarget`s are descendants of the entity containing
`AnimationPlayer` is now just a convention, not a requirement; this
allows us to eliminate the unsafe code.

# Migration guide

* `AnimationClip` now uses UUIDs instead of hierarchical paths based on
the `Name` component to refer to bones. This has several consequences:
- A new component, `AnimationTarget`, should be placed on each bone that
you wish to animate, in order to specify its UUID and the associated
`AnimationPlayer`. The glTF loader automatically creates these
components as necessary, so most uses of glTF rigs shouldn't need to
change.
- Moving a bone around the tree, or renaming it, no longer prevents an
`AnimationPlayer` from affecting it.
- Dynamically changing the `AnimationPlayer` component will likely
require manual updating of the `AnimationTarget` components.
* Entities with `AnimationPlayer` components may now possess descendants
that also have `AnimationPlayer` components. They may not, however,
animate the same bones.
* As they aren't specific to `TypeId`s,
`bevy_reflect::utility::NoOpTypeIdHash` and
`bevy_reflect::utility::NoOpTypeIdHasher` have been renamed to
`bevy_reflect::utility::NoOpHash` and
`bevy_reflect::utility::NoOpHasher` respectively.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Animation Make things move and change over time C-Feature A new feature, making something new possible
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants