Keyframes: a Hold key no longer eases the motion into it

segment_value treated any non-Linear in-side of the next key as Bezier,
so a key set to Hold (KeyframeInterpolationType.HOLD, add_keyframe
"interpolation": "hold", or the Keyframe Interpolation dialog, which set
both sides) turned the linear segment into it into an ease-in with the
default ease: 0 -> 100 over 2 s read 27.0 instead of 25 at 0.5 s. Hold
only affects what follows the key, which the out side already handles.
The Graph Editor and the Lottie exporter already treat a Hold in-side as
linear, so exports didn't match the preview either.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
storybored379andClaude Opus 5.5 committed 2026-10-04 01:17:34 -07:00
1 parent 51c21931ca
commit 3411fc80ab
1 file changed
+14 -1
+14 -1
View File
@@ -441,7 +441,9 @@ pub fn segment_value(keys: &[Keyframe], i: usize, t: Tick, spatial: bool) -> Val
let dur = (t1 - t0).max(1e-12);
let x = ((secs(t) - t0) / dur).clamp(0.0, 1.0);
let lin_out = a.out_interp == Interp::Linear;
let lin_in = b.in_interp == Interp::Linear;
// A Hold in-side only matters for the segment it ends, which the out side above decides:
// it shapes the incoming segment like a linear side, as in the Graph Editor and Lottie export.
let lin_in = b.in_interp != Interp::Bezier;
let is_spatial = spatial && matches!(a.value, Value::Vec2(_) | Value::Vec3(_));
if is_spatial {
let p0 = v3(&a.value);
@@ -584,6 +586,17 @@ mod tests {
assert_eq!(evaluate(&held, s(1.999), false), Some(Value::Scalar(0.0)));
}
#[test]
fn hold_key_keeps_its_incoming_segment_linear() {
// Hold on the second key (in and out, as KeyframeInterpolationType.HOLD sets it) holds
// what comes after it, not the linear motion into it.
let keys = vec![Keyframe::new(s(0.0), Value::Scalar(0.0)), Keyframe::new(s(2.0), Value::Scalar(100.0)).hold()];
assert_eq!(evaluate(&keys, s(0.5), false), Some(Value::Scalar(25.0)));
let keys = vec![Keyframe::new(s(0.0), Value::Vec2([0.0, 0.0])), Keyframe::new(s(2.0), Value::Vec2([100.0, 0.0])).hold()];
let p = evaluate(&keys, s(0.5), true).unwrap().as_vec2();
assert!((p[0] - 25.0).abs() < 0.01, "{p:?}");
}
#[test]
fn easy_ease_is_symmetric_and_slow_at_ends() {
let keys = vec![Keyframe::new(s(0.0), Value::Scalar(0.0)).eased(), Keyframe::new(s(1.0), Value::Scalar(100.0)).eased()];