Skip to main content

alpha2_model/
animation.rs

1use crate::{CubicBezier, Easing};
2use serde::{Deserialize, Serialize};
3
4/// A two-dimensional value in normalized output coordinates.
5#[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
6#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
7#[serde(deny_unknown_fields)]
8pub struct Vec2 {
9    /// Horizontal component.
10    pub x: f32,
11    /// Vertical component.
12    pub y: f32,
13}
14
15impl Vec2 {
16    /// Creates a two-dimensional value.
17    #[must_use]
18    pub const fn new(x: f32, y: f32) -> Self {
19        Self { x, y }
20    }
21
22    fn is_finite(self) -> bool {
23        self.x.is_finite() && self.y.is_finite()
24    }
25
26    /// Whether both components are zero (either sign).
27    #[must_use]
28    pub fn is_zero(&self) -> bool {
29        self.x == 0.0 && self.y == 0.0
30    }
31}
32
33/// A value that is either constant or controlled by sorted keyframes.
34#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
35#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
36#[serde(deny_unknown_fields)]
37#[serde(tag = "mode", rename_all = "snake_case")]
38#[cfg_attr(feature = "schema", schemars(rename = "Animated_{T}"))]
39pub enum Animated<T> {
40    /// A value without animation.
41    Constant {
42        /// Value used at every timestamp.
43        value: T,
44    },
45    /// Values evaluated from their keyframe sequence.
46    Keyframes {
47        /// Strictly ascending keyframes. The evaluator clamps outside the sequence.
48        keyframes: Vec<Keyframe<T>>,
49    },
50}
51
52impl<T> Animated<T> {
53    /// The same animation with each value mapped by `map`, its keyframes'
54    /// times and interpolations kept.
55    #[must_use]
56    pub fn map<U>(&self, map: impl Fn(&T) -> U) -> Animated<U> {
57        match self {
58            Self::Constant { value } => Animated::Constant { value: map(value) },
59            Self::Keyframes { keyframes } => Animated::Keyframes {
60                keyframes: keyframes
61                    .iter()
62                    .map(|keyframe| Keyframe {
63                        time: keyframe.time,
64                        value: map(&keyframe.value),
65                        interpolation: keyframe.interpolation,
66                    })
67                    .collect(),
68            },
69        }
70    }
71}
72
73/// A position that can move along curves between its keyframes.
74///
75/// Its JSON is that of an `Animated<Vec2>`, plus optional spatial tangents on
76/// each keyframe: with them, the value travels along the cubic Bezier from
77/// one keyframe's value through its `out_tangent` and the next keyframe's
78/// `in_tangent`, at the speed its timing curve sets along the curve's
79/// length. Without them it moves in a straight line, as an `Animated<Vec2>`
80/// does.
81#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
82#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
83#[serde(deny_unknown_fields)]
84#[serde(tag = "mode", rename_all = "snake_case")]
85pub enum AnimatedPoint {
86    /// A value without animation.
87    Constant {
88        /// Value used at every timestamp.
89        value: Vec2,
90    },
91    /// Values evaluated from their keyframe sequence.
92    Keyframes {
93        /// Strictly ascending keyframes. The evaluator clamps outside the sequence.
94        keyframes: Vec<PointKeyframe>,
95    },
96}
97
98impl Default for AnimatedPoint {
99    fn default() -> Self {
100        Self::Constant {
101            value: Vec2::default(),
102        }
103    }
104}
105
106impl From<Animated<Vec2>> for AnimatedPoint {
107    fn from(animation: Animated<Vec2>) -> Self {
108        match animation {
109            Animated::Constant { value } => Self::Constant { value },
110            Animated::Keyframes { keyframes } => Self::Keyframes {
111                keyframes: keyframes
112                    .into_iter()
113                    .map(|keyframe| PointKeyframe {
114                        time: keyframe.time,
115                        value: keyframe.value,
116                        interpolation: keyframe.interpolation,
117                        out_tangent: Vec2::default(),
118                        in_tangent: Vec2::default(),
119                    })
120                    .collect(),
121            },
122        }
123    }
124}
125
126impl AnimatedPoint {
127    /// Whether the value changes over time.
128    #[must_use]
129    pub const fn is_animated(&self) -> bool {
130        matches!(self, Self::Keyframes { .. })
131    }
132}
133
134/// One keyframe of an [`AnimatedPoint`].
135#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
136#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
137#[serde(deny_unknown_fields)]
138pub struct PointKeyframe {
139    /// Timestamp of this value, as a [`Keyframe`]'s.
140    pub time: alpha2_time::MediaTime,
141    /// Value at `time`.
142    pub value: Vec2,
143    /// Interpolation from this keyframe to its successor: how fast the value
144    /// travels along the way there.
145    pub interpolation: Interpolation,
146    /// The start of the way to the next keyframe's value, relative to this
147    /// value: the curve's first control point.
148    #[serde(default, skip_serializing_if = "Vec2::is_zero")]
149    pub out_tangent: Vec2,
150    /// The end of the way here from the previous keyframe's value, relative
151    /// to this value: that curve's second control point.
152    #[serde(default, skip_serializing_if = "Vec2::is_zero")]
153    pub in_tangent: Vec2,
154}
155
156/// One timestamped control value.
157#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
158#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
159#[serde(deny_unknown_fields)]
160#[cfg_attr(feature = "schema", schemars(rename = "Keyframe_{T}"))]
161pub struct Keyframe<T> {
162    /// Timestamp of this value: timeline time, except in a composition
163    /// node's settings, which are keyed in composition time, and in a
164    /// shapes or layout generator's tree, which is keyed in its picture's
165    /// time.
166    pub time: alpha2_time::MediaTime,
167    /// Value at `time`.
168    pub value: T,
169    /// Interpolation from this keyframe to its successor.
170    pub interpolation: Interpolation,
171}
172
173/// Interpolation from one keyframe to the next.
174///
175/// Values are interpolated numerically: a rotation from 0 to 720 degrees
176/// turns twice, and one from 0 to 360 turns once. A host that wants the
177/// shortest path normalizes its keyframe values.
178#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
179#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
180#[serde(tag = "type", rename_all = "snake_case", from = "InterpolationWire")]
181pub enum Interpolation {
182    /// Keep this keyframe's value until the next keyframe.
183    Hold,
184    /// Move to the next value at a constant rate.
185    Linear,
186    /// Move to the next value along a CSS `cubic-bezier()` timing curve.
187    CubicBezier(CubicBezier),
188}
189
190/// The wire form of [`Interpolation`]; see `EasingWire` for why the unit
191/// variants are empty structs here.
192#[derive(Deserialize)]
193#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
194#[cfg_attr(feature = "schema", schemars(rename = "Interpolation"))]
195#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
196pub(crate) enum InterpolationWire {
197    /// Keep this keyframe's value until the next keyframe.
198    Hold {},
199    /// Move to the next value at a constant rate.
200    Linear {},
201    /// Move to the next value along a CSS `cubic-bezier()` timing curve.
202    CubicBezier(CubicBezier),
203}
204
205impl From<InterpolationWire> for Interpolation {
206    fn from(wire: InterpolationWire) -> Self {
207        match wire {
208            InterpolationWire::Hold {} => Self::Hold,
209            InterpolationWire::Linear {} => Self::Linear,
210            InterpolationWire::CubicBezier(curve) => Self::CubicBezier(curve),
211        }
212    }
213}
214
215impl Interpolation {
216    /// The fraction of the way to the next value at linear segment
217    /// `progress` (clamped to `0..=1`), or `None` while holding.
218    ///
219    /// A cubic Bezier curve may overshoot, so the fraction can leave `0..=1`.
220    #[must_use]
221    pub fn amount(self, progress: f64) -> Option<f64> {
222        match self {
223            Self::Hold => None,
224            Self::Linear => Some(Easing::Linear.apply(progress)),
225            Self::CubicBezier(curve) => Some(curve.apply(progress)),
226        }
227    }
228
229    /// Whether the interpolation's parameters are usable.
230    #[must_use]
231    pub fn is_valid(self) -> bool {
232        match self {
233            Self::Hold | Self::Linear => true,
234            Self::CubicBezier(curve) => curve.is_valid(),
235        }
236    }
237}
238
239/// Resolution-independent visual presentation controls for a clip.
240#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
241#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
242#[serde(deny_unknown_fields)]
243pub struct VisualProperties {
244    /// Animated normalized canvas translation, along spatial curves where its
245    /// keyframes have tangents.
246    pub position: AnimatedPoint,
247    /// Unanimated normalized point within the source rectangle.
248    pub anchor: Vec2,
249    /// Animated per-axis scale.
250    pub scale: Animated<Vec2>,
251    /// Animated clockwise rotation in degrees.
252    pub rotation_degrees: Animated<f32>,
253    /// Animated opacity constrained to `0..=1`.
254    pub opacity: Animated<f32>,
255}
256
257impl Default for VisualProperties {
258    fn default() -> Self {
259        Self {
260            position: AnimatedPoint::Constant {
261                value: Vec2::new(0.5, 0.5),
262            },
263            anchor: Vec2::new(0.5, 0.5),
264            scale: Animated::Constant {
265                value: Vec2::new(1.0, 1.0),
266            },
267            rotation_degrees: Animated::Constant { value: 0.0 },
268            opacity: Animated::Constant { value: 1.0 },
269        }
270    }
271}
272
273impl VisualProperties {
274    pub(crate) fn validate(&self) -> bool {
275        self.anchor.is_finite()
276            && valid_point_animation(&self.position)
277            && valid_vec2_animation(&self.scale)
278            && valid_scalar_animation(&self.rotation_degrees, false)
279            && valid_scalar_animation(&self.opacity, true)
280    }
281}
282
283fn valid_vec2_animation(animation: &Animated<Vec2>) -> bool {
284    match animation {
285        Animated::Constant { value } => value.is_finite(),
286        Animated::Keyframes { keyframes } => {
287            valid_keyframes(keyframes)
288                && keyframes.iter().all(|keyframe| keyframe.value.is_finite())
289        }
290    }
291}
292
293/// Valid keyframes (or a constant) whose values and tangents are finite.
294pub(crate) fn valid_point_animation(animation: &AnimatedPoint) -> bool {
295    match animation {
296        AnimatedPoint::Constant { value } => value.is_finite(),
297        AnimatedPoint::Keyframes { keyframes } => {
298            !keyframes.is_empty()
299                && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
300                && keyframes.iter().all(|keyframe| {
301                    keyframe.interpolation.is_valid()
302                        && keyframe.value.is_finite()
303                        && keyframe.out_tangent.is_finite()
304                        && keyframe.in_tangent.is_finite()
305                })
306        }
307    }
308}
309
310fn valid_scalar_animation(animation: &Animated<f32>, unit_interval: bool) -> bool {
311    let valid_value =
312        |value: f32| value.is_finite() && (!unit_interval || (0.0..=1.0).contains(&value));
313    match animation {
314        Animated::Constant { value } => valid_value(*value),
315        Animated::Keyframes { keyframes } => {
316            valid_keyframes(keyframes)
317                && keyframes.iter().all(|keyframe| valid_value(keyframe.value))
318        }
319    }
320}
321
322/// Valid keyframes (or a constant) whose every finite value passes `check`.
323pub(crate) fn valid_scalar_values(animation: &Animated<f32>, check: impl Fn(f32) -> bool) -> bool {
324    let valid = |value: f32| value.is_finite() && check(value);
325    match animation {
326        Animated::Constant { value } => valid(*value),
327        Animated::Keyframes { keyframes } => {
328            valid_keyframes(keyframes) && keyframes.iter().all(|keyframe| valid(keyframe.value))
329        }
330    }
331}
332
333/// A non-empty, strictly ascending sequence whose interpolations are usable.
334pub(crate) fn valid_keyframes<T>(keyframes: &[Keyframe<T>]) -> bool {
335    !keyframes.is_empty()
336        && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
337        && keyframes
338            .iter()
339            .all(|keyframe| keyframe.interpolation.is_valid())
340}