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.
This commit is contained in:
robtfm 2024-08-15 16:10:23 +01:00 committed by François
parent 0793d05256
commit 650e7c9eb4
No known key found for this signature in database

View File

@ -155,6 +155,29 @@ impl VariableCurve {
Some(step_start)
}
/// Find the index of the keyframe at or before the current time.
///
/// Returns the first keyframe if the `seek_time` is before the first keyframe, and
/// the second-to-last keyframe if the `seek_time` is after the last keyframe.
/// Panics if there are less than 2 keyframes.
pub fn find_interpolation_start_keyframe(&self, seek_time: f32) -> usize {
// An Ok(keyframe_index) result means an exact result was found by binary search
// An Err result means the keyframe was not found, and the index is the keyframe
// PERF: finding the current keyframe can be optimised
let search_result = self
.keyframe_timestamps
.binary_search_by(|probe| probe.partial_cmp(&seek_time).unwrap());
// We want to find the index of the keyframe before the current time
// If the keyframe is past the second-to-last keyframe, the animation cannot be interpolated.
match search_result {
// An exact match was found
Ok(i) => i.clamp(0, self.keyframe_timestamps.len() - 2),
// No exact match was found, so return the previous keyframe to interpolate from.
Err(i) => (i.saturating_sub(1)).clamp(0, self.keyframe_timestamps.len() - 2),
}
}
}
/// Interpolation method to use between keyframes.
@ -877,15 +900,13 @@ impl AnimationTargetContext<'_> {
continue;
}
// Find the current keyframe
let Some(step_start) = curve.find_current_keyframe(seek_time) else {
continue;
};
// Find the best keyframe to interpolate from
let step_start = curve.find_interpolation_start_keyframe(seek_time);
let timestamp_start = curve.keyframe_timestamps[step_start];
let timestamp_end = curve.keyframe_timestamps[step_start + 1];
// Compute how far we are through the keyframe, normalized to [0, 1]
let lerp = f32::inverse_lerp(timestamp_start, timestamp_end, seek_time);
let lerp = f32::inverse_lerp(timestamp_start, timestamp_end, seek_time).clamp(0.0, 1.0);
self.apply_tweened_keyframe(
curve,