Skip to main content

wde_animation/
player.rs

1use bevy::prelude::*;
2
3use crate::clip::AnimationClip;
4
5/// Rigid part of an animated model, posed by the [`AnimationPlayer`] of its closest ancestor.
6#[derive(Component, Clone, Debug)]
7pub struct AnimationBone {
8    pub name: String,
9    /// Local transform when no clip animates the bone.
10    pub rest: Transform
11}
12
13/// Player animating a bone.
14#[derive(Component)]
15pub(crate) struct BoneOf(Entity);
16
17/// A clip being played.
18#[derive(Clone, Debug)]
19struct PlayingClip {
20    clip: Handle<AnimationClip>,
21    time: f32,
22    speed: f32,
23    /// Loops, or stops on its last pose.
24    repeat: bool,
25    finished: bool
26}
27impl PlayingClip {
28    fn new(clip: Handle<AnimationClip>, speed: f32, repeat: bool) -> Self {
29        Self {
30            clip,
31            time: 0.0,
32            speed,
33            repeat,
34            finished: false
35        }
36    }
37}
38
39/// Plays clips (looping, or once) on the [`AnimationBone`]s below its entity, blending smoothly
40/// from one clip to the next.
41#[derive(Component, Clone, Debug, Default)]
42pub struct AnimationPlayer {
43    active: Option<PlayingClip>,
44    /// Previous clip, fading out.
45    previous: Option<PlayingClip>,
46    fade: f32,
47    fade_duration: f32
48}
49impl AnimationPlayer {
50    /// Plays `clip` right away, from its start.
51    pub fn play(&mut self, clip: Handle<AnimationClip>) -> &mut Self {
52        self.active = Some(PlayingClip::new(clip, 1.0, true));
53        self.previous = None;
54        self
55    }
56
57    /// Blends to `clip` over `fade` seconds and plays it once, from its start (even if it is
58    /// already playing), holding its last pose. See [`Self::is_finished`].
59    pub fn play_once(&mut self, clip: Handle<AnimationClip>, fade: f32) -> &mut Self {
60        self.previous = self.active.take();
61        self.active = Some(PlayingClip::new(clip, 1.0, false));
62        self.fade = 0.0;
63        self.fade_duration = fade;
64        self
65    }
66
67    /// Whether the clip played once is over.
68    pub fn is_finished(&self) -> bool {
69        self.active.as_ref().is_some_and(|a| a.finished)
70    }
71
72    /// Blends from the current clip to `clip` over `duration` seconds. Keeps playing if `clip`
73    /// is already the active clip.
74    pub fn crossfade(&mut self, clip: Handle<AnimationClip>, duration: f32) -> &mut Self {
75        if self.is_playing(&clip) {
76            return self;
77        }
78        let speed = self.active.as_ref().map_or(1.0, |a| a.speed);
79        self.previous = self.active.take();
80        self.active = Some(PlayingClip::new(clip, speed, true));
81        self.fade = 0.0;
82        self.fade_duration = duration;
83        self
84    }
85
86    /// Playback speed of the active clip (1 = authored speed).
87    pub fn set_speed(&mut self, speed: f32) -> &mut Self {
88        if let Some(active) = &mut self.active {
89            active.speed = speed;
90        }
91        self
92    }
93
94    /// Stops animating: the bones go back to their rest pose.
95    pub fn stop(&mut self) {
96        self.active = None;
97        self.previous = None;
98    }
99
100    pub fn is_playing(&self, clip: &Handle<AnimationClip>) -> bool {
101        self.active.as_ref().is_some_and(|a| a.clip == *clip)
102    }
103
104    /// Weight of the active clip against the previous one.
105    fn blend(&self) -> f32 {
106        match self.previous {
107            Some(_) => (self.fade / self.fade_duration.max(f32::EPSILON)).min(1.0),
108            None => 1.0
109        }
110    }
111}
112
113/// Links new bones to the player of their closest ancestor.
114pub(crate) fn link_bones(
115    mut commands: Commands,
116    bones: Query<Entity, (With<AnimationBone>, Without<BoneOf>)>,
117    parents: Query<&ChildOf>,
118    players: Query<(), With<AnimationPlayer>>
119) {
120    for bone in &bones {
121        if let Some(player) = parents.iter_ancestors(bone).find(|e| players.contains(*e)) {
122            commands.entity(bone).insert(BoneOf(player));
123        }
124    }
125}
126
127pub(crate) fn advance_players(
128    mut players: Query<&mut AnimationPlayer>,
129    clips: Res<Assets<AnimationClip>>,
130    time: Res<Time>
131) {
132    let dt = time.delta_secs();
133    for mut player in &mut players {
134        let player = player.as_mut();
135        for playing in [&mut player.active, &mut player.previous]
136            .into_iter()
137            .flatten()
138        {
139            let duration = clips.get(&playing.clip).map_or(0.0, |c| c.duration);
140            if duration <= 0.0 {
141                playing.time = 0.0;
142            } else if playing.repeat {
143                playing.time = (playing.time + dt * playing.speed).rem_euclid(duration);
144            } else {
145                playing.time = (playing.time + dt * playing.speed).min(duration);
146                playing.finished = playing.time >= duration;
147            }
148        }
149        if player.previous.is_some() {
150            player.fade += dt;
151            if player.fade >= player.fade_duration {
152                player.previous = None;
153            }
154        }
155    }
156}
157
158pub(crate) fn pose_bones(
159    mut bones: Query<(&AnimationBone, &BoneOf, &mut Transform)>,
160    players: Query<&AnimationPlayer>,
161    clips: Res<Assets<AnimationClip>>
162) {
163    for (bone, bone_of, mut transform) in &mut bones {
164        let Ok(player) = players.get(bone_of.0) else {
165            continue;
166        };
167        let pose = |playing: &Option<PlayingClip>| {
168            playing.as_ref().map(|p| {
169                clips
170                    .get(&p.clip)
171                    .and_then(|c| c.tracks.get(&bone.name))
172                    .map_or(bone.rest, |track| track.sample(p.time, &bone.rest))
173            })
174        };
175        let active = pose(&player.active).unwrap_or(bone.rest);
176        let posed = match pose(&player.previous) {
177            Some(previous) => {
178                let t = player.blend();
179                Transform {
180                    translation: previous.translation.lerp(active.translation, t),
181                    rotation: previous.rotation.slerp(active.rotation, t),
182                    scale: previous.scale.lerp(active.scale, t)
183                }
184            }
185            None => active
186        };
187        transform.set_if_neq(posed);
188    }
189}