diff options
Diffstat (limited to 'rust/crates/animation/src/keyframes.rs')
| -rw-r--r-- | rust/crates/animation/src/keyframes.rs | 1150 |
1 files changed, 1150 insertions, 0 deletions
diff --git a/rust/crates/animation/src/keyframes.rs b/rust/crates/animation/src/keyframes.rs new file mode 100644 index 00000000..0ce938f9 --- /dev/null +++ b/rust/crates/animation/src/keyframes.rs @@ -0,0 +1,1150 @@ +use std::collections::{BTreeMap, HashMap}; + +use scene::{ + AnimationChannel, ChannelData, DiscreteAnimationKey, DiscreteChannel, DiscreteValue, + ElementAnimations, ParamValue, ScalarAnimationKey, ScalarChannel, ScalarSegmentType, + TangentMode, +}; +use time::MediaTime; + +use crate::bezier::{segment_handles, solve_bezier_progress_for_time}; +use crate::channel_data::{ + get_channel_entries_from_data, get_channels_from_data, is_animation_storage_key, +}; +use crate::channel_layout::{ChannelLayout, ChannelValueKind}; +use crate::id::generate_uuid; +use crate::interpolation::{ + channel_value_at_time, is_scalar_channel, normalize_channel, normalize_discrete_channel, + normalize_scalar_channel, scalar_segment_interpolation, +}; +use crate::types::{AnimationInterpolation, ScalarCurveKeyframePatch}; + +fn round_media_time(time: f64) -> MediaTime { + MediaTime::from_ticks(time.round() as i64) +} + +fn has_channel_keys(channel: Option<&AnimationChannel>) -> bool { + channel.is_some_and(|channel| !channel.keys().is_empty()) +} + +trait ChannelKeys { + fn keys(&self) -> usize; +} + +impl ChannelKeys for AnimationChannel { + fn keys(&self) -> usize { + match self { + AnimationChannel::Scalar(scalar) => scalar.keys.len(), + AnimationChannel::Discrete(discrete) => discrete.keys.len(), + } + } +} + +fn has_channel_data(data: Option<&ChannelData>) -> bool { + get_channels_from_data(data) + .iter() + .any(|channel| has_channel_keys(Some(channel))) +} + +pub fn to_animation(animations: ElementAnimations) -> Option<ElementAnimations> { + let next: ElementAnimations = animations + .into_iter() + .filter(|(key, data)| is_animation_storage_key(key) && has_channel_data(Some(data))) + .collect(); + if next.is_empty() { None } else { Some(next) } +} + +fn get_channel_from_data<'a>( + data: Option<&'a ChannelData>, + component_key: &str, +) -> Option<&'a AnimationChannel> { + match data { + Some(ChannelData::Single(channel)) => { + if component_key == "value" { Some(channel) } else { None } + } + Some(ChannelData::Composite(components)) => components.get(component_key), + None => None, + } +} + +fn get_primary_component_key(channel_layout: &ChannelLayout) -> String { + channel_layout + .components() + .first() + .map(|component| component.key.clone()) + .unwrap_or_else(|| "value".to_string()) +} + +fn get_primary_channel_from_data<'a>( + data: Option<&'a ChannelData>, + channel_layout: &ChannelLayout, +) -> Option<&'a AnimationChannel> { + get_channel_from_data(data, &get_primary_component_key(channel_layout)) +} + +fn set_channel_in_data( + data: Option<ChannelData>, + component_key: &str, + channel: Option<AnimationChannel>, +) -> Option<ChannelData> { + if component_key == "value" { + return channel.map(ChannelData::Single); + } + + let mut components = match data { + Some(ChannelData::Composite(components)) => components, + _ => BTreeMap::new(), + }; + match channel { + Some(channel) if has_channel_keys(Some(&channel)) => { + components.insert(component_key.to_string(), channel); + } + _ => { + components.remove(component_key); + } + } + if components.is_empty() { + None + } else { + Some(ChannelData::Composite(components)) + } +} + +fn scalar_segment_type(interpolation: AnimationInterpolation) -> ScalarSegmentType { + match interpolation { + AnimationInterpolation::Hold => ScalarSegmentType::Step, + AnimationInterpolation::Bezier => ScalarSegmentType::Bezier, + AnimationInterpolation::Linear => ScalarSegmentType::Linear, + } +} + +fn create_scalar_key( + id: String, + time: MediaTime, + value: f64, + interpolation: Option<AnimationInterpolation>, + previous_key: Option<&ScalarAnimationKey>, +) -> ScalarAnimationKey { + ScalarAnimationKey { + id, + time, + value, + left_handle: previous_key.and_then(|key| key.left_handle), + right_handle: previous_key.and_then(|key| key.right_handle), + segment_to_next: previous_key.map(|key| key.segment_to_next).unwrap_or_else(|| { + scalar_segment_type(interpolation.unwrap_or(AnimationInterpolation::Linear)) + }), + tangent_mode: previous_key + .map(|key| key.tangent_mode) + .unwrap_or(TangentMode::Flat), + } +} + +fn channel_key_ids_and_times(channel: &AnimationChannel) -> Vec<(String, MediaTime)> { + match channel { + AnimationChannel::Scalar(scalar) => scalar + .keys + .iter() + .map(|key| (key.id.clone(), key.time)) + .collect(), + AnimationChannel::Discrete(discrete) => discrete + .keys + .iter() + .map(|key| (key.id.clone(), key.time)) + .collect(), + } +} + +fn get_target_key_metadata( + channel: Option<&AnimationChannel>, + time: MediaTime, + keyframe_id: Option<&str>, +) -> (String, MediaTime) { + let normalized = channel.map(normalize_channel); + let keys = normalized + .as_ref() + .map(channel_key_ids_and_times) + .unwrap_or_default(); + + if let Some(id) = keyframe_id { + if let Some((found_id, _)) = keys.iter().find(|(key_id, _)| key_id == id) { + return (found_id.clone(), time); + } + } + + if let Some((found_id, found_time)) = keys.iter().find(|(_, key_time)| *key_time == time) { + return (found_id.clone(), *found_time); + } + + ( + keyframe_id.map(str::to_string).unwrap_or_else(generate_uuid), + time, + ) +} + +pub fn upsert_discrete_channel_key( + channel: Option<&DiscreteChannel>, + time: MediaTime, + value: DiscreteValue, + keyframe_id: Option<&str>, +) -> DiscreteChannel { + let normalized = normalize_discrete_channel(&channel.cloned().unwrap_or(DiscreteChannel { + keys: Vec::new(), + })); + let mut keys = normalized.keys; + + if let Some(id) = keyframe_id { + if let Some(index) = keys.iter().position(|key| key.id == id) { + keys[index] = DiscreteAnimationKey { + id: keys[index].id.clone(), + time, + value, + }; + return normalize_discrete_channel(&DiscreteChannel { keys }); + } + } + + if let Some(index) = keys.iter().position(|key| key.time == time) { + keys[index] = DiscreteAnimationKey { + id: keys[index].id.clone(), + time: keys[index].time, + value, + }; + return normalize_discrete_channel(&DiscreteChannel { keys }); + } + + keys.push(DiscreteAnimationKey { + id: keyframe_id.map(str::to_string).unwrap_or_else(generate_uuid), + time, + value, + }); + normalize_discrete_channel(&DiscreteChannel { keys }) +} + +fn replace_scalar_key_at( + keys: &mut [ScalarAnimationKey], + index: usize, + time: MediaTime, + value: f64, + interpolation: Option<AnimationInterpolation>, + keep_existing_time: bool, +) { + let mut previous_key = keys[index].clone(); + if let Some(interpolation) = interpolation { + previous_key.segment_to_next = scalar_segment_type(interpolation); + } + let key_time = if keep_existing_time { + previous_key.time + } else { + time + }; + keys[index] = create_scalar_key( + previous_key.id.clone(), + key_time, + value, + interpolation, + Some(&previous_key), + ); +} + +pub fn upsert_scalar_channel_key( + channel: Option<&ScalarChannel>, + time: MediaTime, + value: f64, + interpolation: Option<AnimationInterpolation>, + default_interpolation: Option<AnimationInterpolation>, + keyframe_id: Option<&str>, +) -> ScalarChannel { + let normalized = normalize_scalar_channel(&channel.cloned().unwrap_or(ScalarChannel { + keys: Vec::new(), + extrapolation: None, + })); + let mut keys = normalized.keys; + + if let Some(id) = keyframe_id { + if let Some(index) = keys.iter().position(|key| key.id == id) { + replace_scalar_key_at(&mut keys, index, time, value, interpolation, false); + return normalize_scalar_channel(&ScalarChannel { + keys, + extrapolation: normalized.extrapolation, + }); + } + } + + if let Some(index) = keys.iter().position(|key| key.time == time) { + replace_scalar_key_at(&mut keys, index, time, value, interpolation, true); + return normalize_scalar_channel(&ScalarChannel { + keys, + extrapolation: normalized.extrapolation, + }); + } + + keys.push(create_scalar_key( + keyframe_id.map(str::to_string).unwrap_or_else(generate_uuid), + time, + value, + interpolation.or(default_interpolation), + None, + )); + normalize_scalar_channel(&ScalarChannel { + keys, + extrapolation: normalized.extrapolation, + }) +} + +pub fn get_channel<'a>( + animations: Option<&'a ElementAnimations>, + property_path: &str, +) -> Option<&'a AnimationChannel> { + let data = animations?.get(property_path); + match data { + ChannelData::Single(channel) => Some(channel), + ChannelData::Composite(_) => get_channels_from_data(Some(data)).into_iter().next(), + } +} + +pub fn upsert_path_keyframe( + animations: Option<&ElementAnimations>, + property_path: &str, + time: MediaTime, + value: &ParamValue, + interpolation: Option<AnimationInterpolation>, + keyframe_id: Option<&str>, + channel_layout: &ChannelLayout, + coerce_value: impl Fn(&ParamValue) -> Option<ParamValue>, +) -> Option<ElementAnimations> { + let coerced_value = coerce_value(value)?; + + let mut next_animations = animations.cloned().unwrap_or_default(); + let current_data = animations.and_then(|a| a.get(property_path)).cloned(); + let primary_channel = get_primary_channel_from_data(current_data.as_ref(), channel_layout); + let (target_id, target_time) = get_target_key_metadata(primary_channel, time, keyframe_id); + let component_values = channel_layout.decompose(&coerced_value)?; + + let mut next_data = current_data.clone(); + for component in channel_layout.components() { + let component_key = component.key.as_str(); + let Some(next_value) = component_values.get(component_key) else { + continue; + }; + let current_channel = get_channel_from_data(current_data.as_ref(), component_key); + + match component.value_kind { + ChannelValueKind::Discrete => { + let discrete_value = match next_value { + ParamValue::String(value) => DiscreteValue::String(value.clone()), + ParamValue::Bool(value) => DiscreteValue::Bool(*value), + ParamValue::Number(_) => continue, + }; + let discrete_channel = match current_channel { + Some(AnimationChannel::Discrete(discrete)) => Some(discrete), + _ => None, + }; + let next_channel = upsert_discrete_channel_key( + discrete_channel, + target_time, + discrete_value, + Some(&target_id), + ); + next_data = set_channel_in_data( + next_data, + component_key, + Some(AnimationChannel::Discrete(next_channel)), + ); + } + ChannelValueKind::Scalar => { + let ParamValue::Number(number_value) = next_value else { + continue; + }; + let scalar_channel = match current_channel { + Some(AnimationChannel::Scalar(scalar)) => Some(scalar), + _ => None, + }; + let next_channel = upsert_scalar_channel_key( + scalar_channel, + target_time, + *number_value, + interpolation, + Some(component.default_interpolation), + Some(&target_id), + ); + next_data = set_channel_in_data( + next_data, + component_key, + Some(AnimationChannel::Scalar(next_channel)), + ); + } + } + } + + match next_data { + Some(data) => { + next_animations.insert(property_path.to_string(), data); + } + None => { + next_animations.remove(property_path); + } + } + to_animation(next_animations) +} + +pub fn upsert_keyframe( + channel: Option<&AnimationChannel>, + time: MediaTime, + value: &ParamValue, + interpolation: Option<AnimationInterpolation>, + keyframe_id: Option<&str>, +) -> Option<AnimationChannel> { + let channel = channel?; + + match value { + ParamValue::String(value) => { + let discrete = match channel { + AnimationChannel::Discrete(discrete) => Some(discrete), + _ => None, + }; + Some(AnimationChannel::Discrete(upsert_discrete_channel_key( + discrete, + time, + DiscreteValue::String(value.clone()), + keyframe_id, + ))) + } + ParamValue::Bool(value) => { + let discrete = match channel { + AnimationChannel::Discrete(discrete) => Some(discrete), + _ => None, + }; + Some(AnimationChannel::Discrete(upsert_discrete_channel_key( + discrete, + time, + DiscreteValue::Bool(*value), + keyframe_id, + ))) + } + ParamValue::Number(value) => { + let scalar = match channel { + AnimationChannel::Scalar(scalar) => Some(scalar), + _ => None, + }; + Some(AnimationChannel::Scalar(upsert_scalar_channel_key( + scalar, + time, + *value, + interpolation, + None, + keyframe_id, + ))) + } + } +} + +pub fn remove_keyframe( + channel: Option<&AnimationChannel>, + keyframe_id: &str, +) -> Option<AnimationChannel> { + let channel = channel?; + + match channel { + AnimationChannel::Scalar(scalar) => { + let keys: Vec<ScalarAnimationKey> = scalar + .keys + .iter() + .filter(|key| key.id != keyframe_id) + .cloned() + .collect(); + if keys.is_empty() { + return None; + } + Some(AnimationChannel::Scalar(normalize_scalar_channel( + &ScalarChannel { + keys, + extrapolation: scalar.extrapolation.clone(), + }, + ))) + } + AnimationChannel::Discrete(discrete) => { + let keys: Vec<DiscreteAnimationKey> = discrete + .keys + .iter() + .filter(|key| key.id != keyframe_id) + .cloned() + .collect(); + if keys.is_empty() { + return None; + } + Some(AnimationChannel::Discrete(normalize_discrete_channel( + &DiscreteChannel { keys }, + ))) + } + } +} + +pub fn retime_keyframe( + channel: Option<&AnimationChannel>, + keyframe_id: &str, + time: MediaTime, +) -> Option<AnimationChannel> { + let channel = channel?; + + match channel { + AnimationChannel::Scalar(scalar) => { + let Some(index) = scalar.keys.iter().position(|key| key.id == keyframe_id) else { + return Some(channel.clone()); + }; + let mut keys = scalar.keys.clone(); + keys[index].time = time; + Some(AnimationChannel::Scalar(normalize_scalar_channel( + &ScalarChannel { + keys, + extrapolation: scalar.extrapolation.clone(), + }, + ))) + } + AnimationChannel::Discrete(discrete) => { + let Some(index) = discrete.keys.iter().position(|key| key.id == keyframe_id) else { + return Some(channel.clone()); + }; + let mut keys = discrete.keys.clone(); + keys[index].time = time; + Some(AnimationChannel::Discrete(normalize_discrete_channel( + &DiscreteChannel { keys }, + ))) + } + } +} + +pub fn set_channel( + animations: Option<&ElementAnimations>, + property_path: &str, + channel: Option<&AnimationChannel>, +) -> Option<ElementAnimations> { + set_binding_component_channel(animations, property_path, "value", channel) +} + +pub fn set_binding_component_channel( + animations: Option<&ElementAnimations>, + property_path: &str, + component_key: &str, + channel: Option<&AnimationChannel>, +) -> Option<ElementAnimations> { + let mut next_animations = animations.cloned().unwrap_or_default(); + let prepared = channel + .filter(|channel| has_channel_keys(Some(channel))) + .map(normalize_channel); + let data = set_channel_in_data( + next_animations.get(property_path).cloned(), + component_key, + prepared, + ); + match data { + Some(data) => { + next_animations.insert(property_path.to_string(), data); + } + None => { + next_animations.remove(property_path); + } + } + to_animation(next_animations) +} + +pub fn update_scalar_keyframe_curve( + animations: Option<&ElementAnimations>, + property_path: &str, + component_key: &str, + keyframe_id: &str, + patch: &ScalarCurveKeyframePatch, +) -> Option<ElementAnimations> { + let channel = get_channel_from_data( + animations.and_then(|a| a.get(property_path)), + component_key, + ); + let Some(AnimationChannel::Scalar(scalar)) = channel else { + return animations.cloned(); + }; + + let Some(keyframe_index) = scalar.keys.iter().position(|key| key.id == keyframe_id) else { + return animations.cloned(); + }; + + let mut keys = scalar.keys.clone(); + let current_key = &keys[keyframe_index]; + keys[keyframe_index] = ScalarAnimationKey { + left_handle: patch.left_handle.unwrap_or(current_key.left_handle), + right_handle: patch.right_handle.unwrap_or(current_key.right_handle), + segment_to_next: patch.segment_to_next.unwrap_or(current_key.segment_to_next), + tangent_mode: patch.tangent_mode.unwrap_or(current_key.tangent_mode), + ..current_key.clone() + }; + + set_binding_component_channel( + animations, + property_path, + component_key, + Some(&AnimationChannel::Scalar(ScalarChannel { + keys, + extrapolation: scalar.extrapolation.clone(), + })), + ) +} + +fn clone_channel_with_key_ids( + channel: &AnimationChannel, + key_id_map: &HashMap<String, String>, +) -> AnimationChannel { + let remap = |id: &str| key_id_map.get(id).cloned().unwrap_or_else(|| id.to_string()); + match channel { + AnimationChannel::Scalar(scalar) => AnimationChannel::Scalar(normalize_scalar_channel( + &ScalarChannel { + keys: scalar + .keys + .iter() + .map(|key| ScalarAnimationKey { + id: remap(&key.id), + ..key.clone() + }) + .collect(), + extrapolation: scalar.extrapolation.clone(), + }, + )), + AnimationChannel::Discrete(discrete) => { + AnimationChannel::Discrete(normalize_discrete_channel(&DiscreteChannel { + keys: discrete + .keys + .iter() + .map(|key| DiscreteAnimationKey { + id: remap(&key.id), + ..key.clone() + }) + .collect(), + })) + } + } +} + +pub fn clone_animations( + animations: Option<&ElementAnimations>, + regenerate_keyframe_ids: bool, +) -> Option<ElementAnimations> { + let animations = animations?; + + let mut next_animations = animations.clone(); + for (property_path, data) in animations + .iter() + .filter(|(key, _)| is_animation_storage_key(key)) + { + let channels = get_channels_from_data(Some(data)); + let mut key_id_map = HashMap::new(); + if let Some(primary_channel) = channels.first() { + for (id, _) in channel_key_ids_and_times(primary_channel) { + let next_id = if regenerate_keyframe_ids { + generate_uuid() + } else { + id.clone() + }; + key_id_map.insert(id, next_id); + } + } + + let next_data = match data { + ChannelData::Single(channel) => { + ChannelData::Single(clone_channel_with_key_ids(channel, &key_id_map)) + } + ChannelData::Composite(components) => ChannelData::Composite( + components + .iter() + .map(|(component_key, channel)| { + ( + component_key.clone(), + clone_channel_with_key_ids(channel, &key_id_map), + ) + }) + .collect(), + ), + }; + next_animations.insert(property_path.clone(), next_data); + } + + to_animation(next_animations) +} + +pub fn clamp_animations_to_duration( + animations: Option<&ElementAnimations>, + duration: MediaTime, +) -> Option<ElementAnimations> { + let animations = animations?; + if duration <= MediaTime::ZERO { + return None; + } + split_animations_at_time_with_options(Some(animations), duration, true).0 +} + +#[derive(Clone, Copy, Debug)] +struct Point { + x: f64, + y: f64, +} + +fn lerp_point(left: Point, right: Point, progress: f64) -> Point { + Point { + x: left.x + (right.x - left.x) * progress, + y: left.y + (right.y - left.y) * progress, + } +} + +pub struct SplitChannelResult { + pub left_channel: Option<AnimationChannel>, + pub right_channel: Option<AnimationChannel>, +} + +fn split_discrete_channel_at_time( + channel: Option<&DiscreteChannel>, + split_time: MediaTime, + left_boundary_id: &str, + right_boundary_id: &str, + include_split_boundary: bool, +) -> SplitChannelResult { + let Some(channel) = channel.filter(|channel| !channel.keys.is_empty()) else { + return SplitChannelResult { + left_channel: None, + right_channel: None, + }; + }; + + let normalized = normalize_discrete_channel(channel); + let mut left_keys: Vec<DiscreteAnimationKey> = normalized + .keys + .iter() + .filter(|key| key.time <= split_time) + .cloned() + .collect(); + let mut right_keys: Vec<DiscreteAnimationKey> = normalized + .keys + .iter() + .filter(|key| key.time >= split_time) + .map(|key| DiscreteAnimationKey { + time: key.time - split_time, + ..key.clone() + }) + .collect(); + + if include_split_boundary { + let has_boundary_on_left = left_keys.iter().any(|key| key.time == split_time); + let has_boundary_on_right = right_keys.iter().any(|key| key.time == MediaTime::ZERO); + let boundary_value = discrete_value_at_time( + &normalized, + split_time, + normalized.keys[0].value.clone(), + ); + if !has_boundary_on_left { + left_keys.push(DiscreteAnimationKey { + id: left_boundary_id.to_string(), + time: split_time, + value: boundary_value.clone(), + }); + } + if !has_boundary_on_right { + right_keys.insert( + 0, + DiscreteAnimationKey { + id: right_boundary_id.to_string(), + time: MediaTime::ZERO, + value: boundary_value, + }, + ); + } + } + + SplitChannelResult { + left_channel: if left_keys.is_empty() { + None + } else { + Some(AnimationChannel::Discrete(normalize_discrete_channel( + &DiscreteChannel { keys: left_keys }, + ))) + }, + right_channel: if right_keys.is_empty() { + None + } else { + Some(AnimationChannel::Discrete(normalize_discrete_channel( + &DiscreteChannel { keys: right_keys }, + ))) + }, + } +} + +fn discrete_value_at_time( + channel: &DiscreteChannel, + time: MediaTime, + fallback_value: DiscreteValue, +) -> DiscreteValue { + let ParamValue::Bool(_) = ParamValue::Bool(false) else { + unreachable!() + }; + crate::interpolation::discrete_channel_value_at_time(Some(channel), time, fallback_value) +} + +fn build_split_scalar_result( + left_keys: Vec<ScalarAnimationKey>, + right_keys: Vec<ScalarAnimationKey>, + extrapolation: Option<scene::ChannelExtrapolation>, +) -> SplitChannelResult { + SplitChannelResult { + left_channel: if left_keys.is_empty() { + None + } else { + Some(AnimationChannel::Scalar(normalize_scalar_channel( + &ScalarChannel { + keys: left_keys, + extrapolation: extrapolation.clone(), + }, + ))) + }, + right_channel: if right_keys.is_empty() { + None + } else { + Some(AnimationChannel::Scalar(normalize_scalar_channel( + &ScalarChannel { + keys: right_keys, + extrapolation, + }, + ))) + }, + } +} + +fn split_scalar_channel_at_time( + channel: Option<&ScalarChannel>, + split_time: MediaTime, + left_boundary_id: &str, + right_boundary_id: &str, + include_split_boundary: bool, +) -> SplitChannelResult { + let Some(channel) = channel.filter(|channel| !channel.keys.is_empty()) else { + return SplitChannelResult { + left_channel: None, + right_channel: None, + }; + }; + + let normalized = normalize_scalar_channel(channel); + let mut left_keys: Vec<ScalarAnimationKey> = normalized + .keys + .iter() + .filter(|key| key.time <= split_time) + .cloned() + .collect(); + let mut right_keys: Vec<ScalarAnimationKey> = normalized + .keys + .iter() + .filter(|key| key.time >= split_time) + .map(|key| ScalarAnimationKey { + time: key.time - split_time, + ..key.clone() + }) + .collect(); + + let has_boundary_on_left = left_keys.iter().any(|key| key.time == split_time); + let has_boundary_on_right = right_keys.iter().any(|key| key.time == MediaTime::ZERO); + if !include_split_boundary || (has_boundary_on_left && has_boundary_on_right) { + return build_split_scalar_result( + left_keys, + right_keys, + normalized.extrapolation.clone(), + ); + } + + for key_index in 0..normalized.keys.len().saturating_sub(1) { + let left_key = &normalized.keys[key_index]; + let right_key = &normalized.keys[key_index + 1]; + if split_time <= left_key.time || split_time >= right_key.time { + continue; + } + + let boundary_value = crate::interpolation::scalar_channel_value_at_time( + Some(&normalized), + split_time, + left_key.value, + ); + + if left_key.segment_to_next == ScalarSegmentType::Bezier { + let (right_handle, left_handle) = segment_handles(left_key, right_key); + let progress = solve_bezier_progress_for_time(split_time, left_key, right_key); + let p0 = Point { + x: left_key.time.as_ticks() as f64, + y: left_key.value, + }; + let p1 = Point { + x: p0.x + right_handle.dt, + y: p0.y + right_handle.dv, + }; + let p3 = Point { + x: right_key.time.as_ticks() as f64, + y: right_key.value, + }; + let p2 = Point { + x: p3.x + left_handle.dt, + y: p3.y + left_handle.dv, + }; + let q0 = lerp_point(p0, p1, progress); + let q1 = lerp_point(p1, p2, progress); + let q2 = lerp_point(p2, p3, progress); + let r0 = lerp_point(q0, q1, progress); + let r1 = lerp_point(q1, q2, progress); + let split_point = lerp_point(r0, r1, progress); + + let mut next_left_keys: Vec<ScalarAnimationKey> = normalized + .keys + .iter() + .filter(|key| key.time < split_time) + .cloned() + .collect(); + next_left_keys.push(ScalarAnimationKey { + right_handle: Some(scene::CurveHandle { + dt: round_media_time(q0.x - p0.x), + dv: q0.y - p0.y, + }), + ..left_key.clone() + }); + next_left_keys.push(ScalarAnimationKey { + id: left_boundary_id.to_string(), + time: split_time, + value: boundary_value, + left_handle: Some(scene::CurveHandle { + dt: round_media_time(r0.x - split_point.x), + dv: r0.y - split_point.y, + }), + right_handle: None, + segment_to_next: left_key.segment_to_next, + tangent_mode: left_key.tangent_mode, + }); + + let mut next_right_keys: Vec<ScalarAnimationKey> = vec![ + ScalarAnimationKey { + id: right_boundary_id.to_string(), + time: MediaTime::ZERO, + value: boundary_value, + left_handle: None, + right_handle: Some(scene::CurveHandle { + dt: round_media_time(r1.x - split_point.x), + dv: r1.y - split_point.y, + }), + segment_to_next: ScalarSegmentType::Bezier, + tangent_mode: left_key.tangent_mode, + }, + ScalarAnimationKey { + time: right_key.time - split_time, + left_handle: Some(scene::CurveHandle { + dt: round_media_time(q2.x - p3.x), + dv: q2.y - p3.y, + }), + ..right_key.clone() + }, + ]; + next_right_keys.extend( + normalized + .keys + .iter() + .filter(|key| key.time > right_key.time) + .map(|key| ScalarAnimationKey { + time: key.time - split_time, + ..key.clone() + }), + ); + + return SplitChannelResult { + left_channel: Some(AnimationChannel::Scalar(normalize_scalar_channel( + &ScalarChannel { + keys: next_left_keys, + extrapolation: normalized.extrapolation.clone(), + }, + ))), + right_channel: Some(AnimationChannel::Scalar(normalize_scalar_channel( + &ScalarChannel { + keys: next_right_keys, + extrapolation: normalized.extrapolation.clone(), + }, + ))), + }; + } + + left_keys.push(create_scalar_key( + left_boundary_id.to_string(), + split_time, + boundary_value, + Some(AnimationInterpolation::Linear), + None, + )); + right_keys.insert( + 0, + create_scalar_key( + right_boundary_id.to_string(), + MediaTime::ZERO, + boundary_value, + Some(scalar_segment_interpolation(left_key.segment_to_next)), + None, + ), + ); + return build_split_scalar_result( + left_keys, + right_keys, + normalized.extrapolation.clone(), + ); + } + + build_split_scalar_result(left_keys, right_keys, normalized.extrapolation.clone()) +} + +fn split_channel_at_time( + channel: Option<&AnimationChannel>, + split_time: MediaTime, + left_boundary_id: &str, + right_boundary_id: &str, + include_split_boundary: bool, +) -> SplitChannelResult { + match channel { + Some(AnimationChannel::Discrete(discrete)) => split_discrete_channel_at_time( + Some(discrete), + split_time, + left_boundary_id, + right_boundary_id, + include_split_boundary, + ), + scalar => split_scalar_channel_at_time( + match scalar { + Some(AnimationChannel::Scalar(scalar)) => Some(scalar), + _ => None, + }, + split_time, + left_boundary_id, + right_boundary_id, + include_split_boundary, + ), + } +} + +pub fn split_animations_at_time_with_options( + animations: Option<&ElementAnimations>, + split_time: MediaTime, + include_split_boundary: bool, +) -> (Option<ElementAnimations>, Option<ElementAnimations>) { + let Some(animations) = animations else { + return (None, None); + }; + + let mut left_animations = ElementAnimations::new(); + let mut right_animations = ElementAnimations::new(); + + for (property_path, data) in animations + .iter() + .filter(|(key, _)| is_animation_storage_key(key)) + { + let left_boundary_id = generate_uuid(); + let right_boundary_id = generate_uuid(); + + for (component_key, channel) in get_channel_entries_from_data(Some(data)) { + let split = split_channel_at_time( + Some(channel), + split_time, + &left_boundary_id, + &right_boundary_id, + include_split_boundary, + ); + if let Some(left_channel) = split.left_channel { + if let Some(data) = set_channel_in_data( + left_animations.get(property_path).cloned(), + &component_key, + Some(left_channel), + ) { + left_animations.insert(property_path.clone(), data); + } + } + if let Some(right_channel) = split.right_channel { + if let Some(data) = set_channel_in_data( + right_animations.get(property_path).cloned(), + &component_key, + Some(right_channel), + ) { + right_animations.insert(property_path.clone(), data); + } + } + } + } + + (to_animation(left_animations), to_animation(right_animations)) +} + +pub fn split_animations_at_time( + animations: &ElementAnimations, + split_time: MediaTime, +) -> (ElementAnimations, ElementAnimations) { + let (left, right) = split_animations_at_time_with_options(Some(animations), split_time, true); + (left.unwrap_or_default(), right.unwrap_or_default()) +} + +pub fn remove_element_keyframe( + animations: Option<&ElementAnimations>, + property_path: &str, + keyframe_id: &str, +) -> Option<ElementAnimations> { + let data = animations.and_then(|a| a.get(property_path))?; + + let mut next_animations = animations.cloned().unwrap_or_default(); + let next_data = match data { + ChannelData::Single(channel) => remove_keyframe(Some(channel), keyframe_id), + ChannelData::Composite(components) => { + let mut next_data = Some(data.clone()); + for (component_key, channel) in components { + next_data = set_channel_in_data( + next_data, + component_key, + remove_keyframe(Some(channel), keyframe_id), + ); + } + next_data + } + }; + match next_data { + Some(data) => { + next_animations.insert(property_path.to_string(), data); + } + None => { + next_animations.remove(property_path); + } + } + to_animation(next_animations) +} + +pub fn retime_element_keyframe( + animations: Option<&ElementAnimations>, + property_path: &str, + keyframe_id: &str, + time: MediaTime, +) -> Option<ElementAnimations> { + let data = animations.and_then(|a| a.get(property_path))?; + + let mut next_animations = animations.cloned().unwrap_or_default(); + let next_data = match data { + ChannelData::Single(channel) => retime_keyframe(Some(channel), keyframe_id, time), + ChannelData::Composite(components) => { + let mut next_data = Some(data.clone()); + for (component_key, channel) in components { + next_data = set_channel_in_data( + next_data, + component_key, + retime_keyframe(Some(channel), keyframe_id, time), + ); + } + next_data + } + }; + match next_data { + Some(data) => { + next_animations.insert(property_path.to_string(), data); + } + None => { + next_animations.remove(property_path); + } + } + to_animation(next_animations) +} |
