summaryrefslogtreecommitdiff
path: root/rust/crates/animation/src/interpolation.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/crates/animation/src/interpolation.rs')
-rw-r--r--rust/crates/animation/src/interpolation.rs467
1 files changed, 467 insertions, 0 deletions
diff --git a/rust/crates/animation/src/interpolation.rs b/rust/crates/animation/src/interpolation.rs
new file mode 100644
index 00000000..85c0735d
--- /dev/null
+++ b/rust/crates/animation/src/interpolation.rs
@@ -0,0 +1,467 @@
+use scene::{
+ AnimationChannel, ChannelExtrapolationMode, CurveHandle, DiscreteChannel, DiscreteValue,
+ ParamValue, ScalarAnimationKey, ScalarChannel, ScalarSegmentType,
+};
+use time::MediaTime;
+
+use crate::bezier::{bezier_point, segment_handles, solve_bezier_progress_for_time};
+use crate::types::AnimationInterpolation;
+
+pub fn is_scalar_channel(channel: &AnimationChannel) -> bool {
+ matches!(channel, AnimationChannel::Scalar(_))
+}
+
+fn normalize_right_handle(
+ handle: Option<CurveHandle>,
+ left_key: &ScalarAnimationKey,
+ right_key: &ScalarAnimationKey,
+) -> Option<CurveHandle> {
+ let handle = handle?;
+ let span = (right_key.time - left_key.time).as_ticks().max(1);
+ Some(CurveHandle {
+ dt: MediaTime::from_ticks(handle.dt.as_ticks().clamp(0, span)),
+ dv: handle.dv,
+ })
+}
+
+fn normalize_left_handle(
+ handle: Option<CurveHandle>,
+ left_key: &ScalarAnimationKey,
+ right_key: &ScalarAnimationKey,
+) -> Option<CurveHandle> {
+ let handle = handle?;
+ let span = (right_key.time - left_key.time).as_ticks().max(1);
+ Some(CurveHandle {
+ dt: MediaTime::from_ticks(handle.dt.as_ticks().clamp(-span, 0)),
+ dv: handle.dv,
+ })
+}
+
+pub fn normalize_scalar_channel(channel: &ScalarChannel) -> ScalarChannel {
+ let mut sorted_keys = channel.keys.clone();
+ sorted_keys.sort_by_key(|key| key.time);
+
+ let mut next_keys = Vec::with_capacity(sorted_keys.len());
+ for (index, key) in sorted_keys.iter().enumerate() {
+ let previous_key = index.checked_sub(1).and_then(|i| sorted_keys.get(i));
+ let next_key = sorted_keys.get(index + 1);
+ let left_handle =
+ previous_key.and_then(|previous| normalize_left_handle(key.left_handle, previous, key));
+ let right_handle =
+ next_key.and_then(|next| normalize_right_handle(key.right_handle, key, next));
+ next_keys.push(ScalarAnimationKey {
+ left_handle,
+ right_handle,
+ ..key.clone()
+ });
+ }
+
+ ScalarChannel {
+ keys: next_keys,
+ extrapolation: channel.extrapolation.clone(),
+ }
+}
+
+pub fn normalize_discrete_channel(channel: &DiscreteChannel) -> DiscreteChannel {
+ let mut keys = channel.keys.clone();
+ keys.sort_by_key(|key| key.time);
+ DiscreteChannel { keys }
+}
+
+pub fn normalize_channel(channel: &AnimationChannel) -> AnimationChannel {
+ match channel {
+ AnimationChannel::Scalar(scalar) => {
+ AnimationChannel::Scalar(normalize_scalar_channel(scalar))
+ }
+ AnimationChannel::Discrete(discrete) => {
+ AnimationChannel::Discrete(normalize_discrete_channel(discrete))
+ }
+ }
+}
+
+pub fn scalar_segment_interpolation(segment: ScalarSegmentType) -> AnimationInterpolation {
+ match segment {
+ ScalarSegmentType::Step => AnimationInterpolation::Hold,
+ ScalarSegmentType::Linear => AnimationInterpolation::Linear,
+ ScalarSegmentType::Bezier => AnimationInterpolation::Bezier,
+ }
+}
+
+fn extrapolate_scalar_edge(
+ mode: ChannelExtrapolationMode,
+ edge_key: &ScalarAnimationKey,
+ neighbor_key: Option<&ScalarAnimationKey>,
+ time: f64,
+) -> f64 {
+ let Some(neighbor_key) = neighbor_key else {
+ return edge_key.value;
+ };
+ if mode == ChannelExtrapolationMode::Hold {
+ return edge_key.value;
+ }
+
+ let span = (neighbor_key.time - edge_key.time).as_ticks() as f64;
+ if span == 0.0 {
+ return edge_key.value;
+ }
+
+ edge_key.value
+ + ((time - edge_key.time.as_ticks() as f64) / span) * (neighbor_key.value - edge_key.value)
+}
+
+fn lerp_number(left_value: f64, right_value: f64, progress: f64) -> f64 {
+ left_value + (right_value - left_value) * progress
+}
+
+pub fn scalar_channel_value_at_time(
+ channel: Option<&ScalarChannel>,
+ time: MediaTime,
+ fallback_value: f64,
+) -> f64 {
+ let Some(channel) = channel.filter(|channel| !channel.keys.is_empty()) else {
+ return fallback_value;
+ };
+
+ let normalized = normalize_scalar_channel(channel);
+ let keys = &normalized.keys;
+ let first_key = &keys[0];
+ let last_key = &keys[keys.len() - 1];
+ let t = time.as_ticks() as f64;
+
+ if time <= first_key.time {
+ if time < first_key.time {
+ return extrapolate_scalar_edge(
+ normalized
+ .extrapolation
+ .as_ref()
+ .map(|e| e.before)
+ .unwrap_or(ChannelExtrapolationMode::Hold),
+ first_key,
+ keys.get(1),
+ t,
+ );
+ }
+ return first_key.value;
+ }
+
+ if time >= last_key.time {
+ if time > last_key.time {
+ return extrapolate_scalar_edge(
+ normalized
+ .extrapolation
+ .as_ref()
+ .map(|e| e.after)
+ .unwrap_or(ChannelExtrapolationMode::Hold),
+ last_key,
+ keys.len().checked_sub(2).and_then(|index| keys.get(index)),
+ t,
+ );
+ }
+ return last_key.value;
+ }
+
+ for key_index in 0..keys.len() - 1 {
+ let left_key = &keys[key_index];
+ let right_key = &keys[key_index + 1];
+ if time == right_key.time {
+ return right_key.value;
+ }
+ if time < left_key.time || time > right_key.time {
+ continue;
+ }
+
+ if left_key.segment_to_next == ScalarSegmentType::Step {
+ return left_key.value;
+ }
+
+ let span = (right_key.time - left_key.time).as_ticks() as f64;
+ if span == 0.0 {
+ return right_key.value;
+ }
+
+ let progress = ((t - left_key.time.as_ticks() as f64) / span).clamp(0.0, 1.0);
+ if left_key.segment_to_next == ScalarSegmentType::Linear {
+ return lerp_number(left_key.value, right_key.value, progress);
+ }
+
+ let curve_progress = solve_bezier_progress_for_time(time, left_key, right_key);
+ let (right_handle, left_handle) = segment_handles(left_key, right_key);
+ return bezier_point(
+ curve_progress,
+ left_key.value,
+ left_key.value + right_handle.dv,
+ right_key.value + left_handle.dv,
+ right_key.value,
+ );
+ }
+
+ last_key.value
+}
+
+pub fn discrete_channel_value_at_time(
+ channel: Option<&DiscreteChannel>,
+ time: MediaTime,
+ fallback_value: DiscreteValue,
+) -> DiscreteValue {
+ let Some(channel) = channel.filter(|channel| !channel.keys.is_empty()) else {
+ return fallback_value;
+ };
+
+ let normalized = normalize_discrete_channel(channel);
+ let mut current_value = fallback_value;
+ for key in &normalized.keys {
+ if time < key.time {
+ break;
+ }
+ current_value = key.value.clone();
+ }
+ current_value
+}
+
+pub fn channel_value_at_time(
+ channel: Option<&AnimationChannel>,
+ time: MediaTime,
+ fallback_value: &ParamValue,
+) -> ParamValue {
+ let Some(channel) = channel.filter(|channel| !channel.keys.is_empty()) else {
+ return fallback_value.clone();
+ };
+
+ if let ParamValue::Number(number_fallback) = fallback_value {
+ return match channel {
+ AnimationChannel::Scalar(scalar) => ParamValue::Number(scalar_channel_value_at_time(
+ Some(scalar),
+ time,
+ *number_fallback,
+ )),
+ AnimationChannel::Discrete(_) => fallback_value.clone(),
+ };
+ }
+
+ match channel {
+ AnimationChannel::Discrete(discrete) => {
+ let discrete_fallback = match fallback_value {
+ ParamValue::Bool(value) => DiscreteValue::Bool(*value),
+ ParamValue::String(value) => DiscreteValue::String(value.clone()),
+ ParamValue::Number(_) => return fallback_value.clone(),
+ };
+ discrete_channel_value_at_time(Some(discrete), time, discrete_fallback).into()
+ }
+ AnimationChannel::Scalar(_) => fallback_value.clone(),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use scene::{ChannelExtrapolation, TangentMode};
+
+ fn scalar_key(id: &str, time: i64, value: f64, segment: ScalarSegmentType) -> ScalarAnimationKey {
+ ScalarAnimationKey {
+ id: id.to_string(),
+ time: MediaTime::from_ticks(time),
+ value,
+ left_handle: None,
+ right_handle: None,
+ segment_to_next: segment,
+ tangent_mode: TangentMode::Flat,
+ }
+ }
+
+ fn channel(keys: Vec<ScalarAnimationKey>) -> ScalarChannel {
+ ScalarChannel {
+ keys,
+ extrapolation: None,
+ }
+ }
+
+ fn at(time: i64) -> MediaTime {
+ MediaTime::from_ticks(time)
+ }
+
+ #[test]
+ fn empty_channel_returns_fallback() {
+ assert_eq!(scalar_channel_value_at_time(None, at(10), 7.0), 7.0);
+ assert_eq!(
+ scalar_channel_value_at_time(Some(&channel(Vec::new())), at(10), 7.0),
+ 7.0
+ );
+ }
+
+ #[test]
+ fn linear_segment_interpolates() {
+ let channel = channel(vec![
+ scalar_key("a", 0, 0.0, ScalarSegmentType::Linear),
+ scalar_key("b", 100, 10.0, ScalarSegmentType::Linear),
+ ]);
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(50), 0.0), 5.0);
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(0), 0.0), 0.0);
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(100), 0.0), 10.0);
+ }
+
+ #[test]
+ fn step_segment_holds_left_value() {
+ let channel = channel(vec![
+ scalar_key("a", 0, 1.0, ScalarSegmentType::Step),
+ scalar_key("b", 100, 9.0, ScalarSegmentType::Linear),
+ ]);
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(99), 0.0), 1.0);
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(100), 0.0), 9.0);
+ }
+
+ #[test]
+ fn unsorted_keys_are_sorted_before_sampling() {
+ let channel = channel(vec![
+ scalar_key("b", 100, 10.0, ScalarSegmentType::Linear),
+ scalar_key("a", 0, 0.0, ScalarSegmentType::Linear),
+ ]);
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(50), 0.0), 5.0);
+ }
+
+ #[test]
+ fn hold_extrapolation_is_the_default() {
+ let channel = channel(vec![
+ scalar_key("a", 10, 1.0, ScalarSegmentType::Linear),
+ scalar_key("b", 100, 10.0, ScalarSegmentType::Linear),
+ ]);
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(0), 0.0), 1.0);
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(200), 0.0), 10.0);
+ }
+
+ #[test]
+ fn linear_extrapolation_extends_edge_segments() {
+ let mut channel = channel(vec![
+ scalar_key("a", 0, 0.0, ScalarSegmentType::Linear),
+ scalar_key("b", 100, 10.0, ScalarSegmentType::Linear),
+ ]);
+ channel.extrapolation = Some(ChannelExtrapolation {
+ before: ChannelExtrapolationMode::Linear,
+ after: ChannelExtrapolationMode::Linear,
+ });
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(-50), 0.0), -5.0);
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(150), 0.0), 15.0);
+ }
+
+ #[test]
+ fn linear_extrapolation_holds_with_single_key() {
+ let mut channel = channel(vec![scalar_key("a", 10, 3.0, ScalarSegmentType::Linear)]);
+ channel.extrapolation = Some(ChannelExtrapolation {
+ before: ChannelExtrapolationMode::Linear,
+ after: ChannelExtrapolationMode::Linear,
+ });
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(0), 0.0), 3.0);
+ assert_eq!(scalar_channel_value_at_time(Some(&channel), at(50), 0.0), 3.0);
+ }
+
+ #[test]
+ fn bezier_segment_with_default_handles_matches_diagonal() {
+ let channel = channel(vec![
+ scalar_key("a", 0, 0.0, ScalarSegmentType::Bezier),
+ scalar_key("b", 100, 100.0, ScalarSegmentType::Bezier),
+ ]);
+ let value = scalar_channel_value_at_time(Some(&channel), at(50), 0.0);
+ assert!((value - 50.0).abs() < 1e-6);
+ }
+
+ #[test]
+ fn bezier_segment_with_flat_handles_eases() {
+ let mut left = scalar_key("a", 0, 0.0, ScalarSegmentType::Bezier);
+ left.right_handle = Some(CurveHandle {
+ dt: at(50),
+ dv: 0.0,
+ });
+ let mut right = scalar_key("b", 100, 100.0, ScalarSegmentType::Bezier);
+ right.left_handle = Some(CurveHandle {
+ dt: at(-50),
+ dv: 0.0,
+ });
+ let channel = channel(vec![left, right]);
+ let quarter = scalar_channel_value_at_time(Some(&channel), at(25), 0.0);
+ let mid = scalar_channel_value_at_time(Some(&channel), at(50), 0.0);
+ assert!(quarter < 25.0);
+ assert!((mid - 50.0).abs() < 1e-6);
+ }
+
+ #[test]
+ fn handles_are_clamped_to_segment_span() {
+ let mut left = scalar_key("a", 0, 0.0, ScalarSegmentType::Bezier);
+ left.right_handle = Some(CurveHandle {
+ dt: at(500),
+ dv: 1.0,
+ });
+ let mut right = scalar_key("b", 100, 100.0, ScalarSegmentType::Bezier);
+ right.left_handle = Some(CurveHandle {
+ dt: at(50),
+ dv: 1.0,
+ });
+ let normalized = normalize_scalar_channel(&channel(vec![left, right]));
+ assert_eq!(normalized.keys[0].right_handle.unwrap().dt, at(100));
+ assert_eq!(normalized.keys[1].left_handle.unwrap().dt, at(0));
+ }
+
+ #[test]
+ fn edge_keys_drop_outer_handles() {
+ let mut left = scalar_key("a", 0, 0.0, ScalarSegmentType::Bezier);
+ left.left_handle = Some(CurveHandle {
+ dt: at(-10),
+ dv: 1.0,
+ });
+ let mut right = scalar_key("b", 100, 100.0, ScalarSegmentType::Bezier);
+ right.right_handle = Some(CurveHandle {
+ dt: at(10),
+ dv: 1.0,
+ });
+ let normalized = normalize_scalar_channel(&channel(vec![left, right]));
+ assert_eq!(normalized.keys[0].left_handle, None);
+ assert_eq!(normalized.keys[1].right_handle, None);
+ }
+
+ #[test]
+ fn discrete_channel_returns_last_key_at_or_before_time() {
+ let channel = DiscreteChannel {
+ keys: vec![
+ DiscreteAnimationKey {
+ id: "a".to_string(),
+ time: at(0),
+ value: DiscreteValue::String("x".to_string()),
+ },
+ DiscreteAnimationKey {
+ id: "b".to_string(),
+ time: at(100),
+ value: DiscreteValue::Bool(true),
+ },
+ ],
+ };
+ let fallback = DiscreteValue::Bool(false);
+ assert_eq!(
+ discrete_channel_value_at_time(Some(&channel), at(-1), fallback.clone()),
+ fallback
+ );
+ assert_eq!(
+ discrete_channel_value_at_time(Some(&channel), at(50), fallback.clone()),
+ DiscreteValue::String("x".to_string())
+ );
+ assert_eq!(
+ discrete_channel_value_at_time(Some(&channel), at(100), fallback.clone()),
+ DiscreteValue::Bool(true)
+ );
+ }
+
+ #[test]
+ fn channel_value_at_time_dispatches_on_fallback_type() {
+ let scalar = AnimationChannel::Scalar(channel(vec![scalar_key(
+ "a",
+ 0,
+ 2.0,
+ ScalarSegmentType::Linear,
+ )]));
+ assert_eq!(
+ channel_value_at_time(Some(&scalar), at(0), &ParamValue::Number(0.0)),
+ ParamValue::Number(2.0)
+ );
+ assert_eq!(
+ channel_value_at_time(Some(&scalar), at(0), &ParamValue::Bool(true)),
+ ParamValue::Bool(true)
+ );
+ }
+}