Skip to content

Commit 9ca5540

Browse files
authored
apply finished animations (#14743)
# Objective fix #14742 ## Solution the issue arises because "finished" animations (where current time >= last keyframe time) are not applied at all. when transitioning from a finished animation to another later-indexed anim, the transition kind-of works because the finished anim is skipped, then the new anim is applied with a lower weight (weight / total_weight) when transitioning from a finished animation to another earlier-indexed anim, the transition is instant as the new anim is applied with 1.0 (as weight == total_weight for the first applied), then the finished animation is skipped. to fix this we can always apply every animation based on the nearest 2 keyframes, and clamp the interpolation between them to [0,1]. pros: - finished animations can be transitioned out of correctly - blended animations where some curves have a last-keyframe before the end of the animation will blend properly - animations will actually finish on their last keyframe, rather than a fraction of a render-frame before the end cons: - we have to re-apply finished animations every frame whether it's necessary or not. i can't see a way to avoid this.
1 parent 6adf31b commit 9ca5540

File tree

1 file changed

+26
-5
lines changed
  • crates/bevy_animation/src

1 file changed

+26
-5
lines changed

crates/bevy_animation/src/lib.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,29 @@ impl VariableCurve {
155155

156156
Some(step_start)
157157
}
158+
159+
/// Find the index of the keyframe at or before the current time.
160+
///
161+
/// Returns the first keyframe if the `seek_time` is before the first keyframe, and
162+
/// the second-to-last keyframe if the `seek_time` is after the last keyframe.
163+
/// Panics if there are less than 2 keyframes.
164+
pub fn find_interpolation_start_keyframe(&self, seek_time: f32) -> usize {
165+
// An Ok(keyframe_index) result means an exact result was found by binary search
166+
// An Err result means the keyframe was not found, and the index is the keyframe
167+
// PERF: finding the current keyframe can be optimised
168+
let search_result = self
169+
.keyframe_timestamps
170+
.binary_search_by(|probe| probe.partial_cmp(&seek_time).unwrap());
171+
172+
// We want to find the index of the keyframe before the current time
173+
// If the keyframe is past the second-to-last keyframe, the animation cannot be interpolated.
174+
match search_result {
175+
// An exact match was found
176+
Ok(i) => i.clamp(0, self.keyframe_timestamps.len() - 2),
177+
// No exact match was found, so return the previous keyframe to interpolate from.
178+
Err(i) => (i.saturating_sub(1)).clamp(0, self.keyframe_timestamps.len() - 2),
179+
}
180+
}
158181
}
159182

160183
/// Interpolation method to use between keyframes.
@@ -878,15 +901,13 @@ impl AnimationTargetContext<'_> {
878901
continue;
879902
}
880903

881-
// Find the current keyframe
882-
let Some(step_start) = curve.find_current_keyframe(seek_time) else {
883-
continue;
884-
};
904+
// Find the best keyframe to interpolate from
905+
let step_start = curve.find_interpolation_start_keyframe(seek_time);
885906

886907
let timestamp_start = curve.keyframe_timestamps[step_start];
887908
let timestamp_end = curve.keyframe_timestamps[step_start + 1];
888909
// Compute how far we are through the keyframe, normalized to [0, 1]
889-
let lerp = f32::inverse_lerp(timestamp_start, timestamp_end, seek_time);
910+
let lerp = f32::inverse_lerp(timestamp_start, timestamp_end, seek_time).clamp(0.0, 1.0);
890911

891912
self.apply_tweened_keyframe(
892913
curve,

0 commit comments

Comments
 (0)