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)]
243///
244/// Each field a document leaves out takes its default: the
245/// source centred on the canvas, unscaled, unrotated and opaque.
246pub struct VisualProperties {
247    /// Animated normalized canvas translation, along spatial curves where its
248    /// keyframes have tangents.
249    #[serde(default = "centre_point")]
250    pub position: AnimatedPoint,
251    /// Unanimated normalized point within the source rectangle.
252    #[serde(default = "centre")]
253    pub anchor: Vec2,
254    /// Animated per-axis scale.
255    #[serde(default = "unit_scale")]
256    pub scale: Animated<Vec2>,
257    /// Animated clockwise rotation in degrees.
258    #[serde(default = "no_rotation")]
259    pub rotation_degrees: Animated<f32>,
260    /// Animated opacity constrained to `0..=1`.
261    #[serde(default = "opaque")]
262    pub opacity: Animated<f32>,
263}
264
265impl Default for VisualProperties {
266    fn default() -> Self {
267        Self {
268            position: centre_point(),
269            anchor: centre(),
270            scale: unit_scale(),
271            rotation_degrees: no_rotation(),
272            opacity: opaque(),
273        }
274    }
275}
276
277const fn centre() -> Vec2 {
278    Vec2::new(0.5, 0.5)
279}
280
281const fn centre_point() -> AnimatedPoint {
282    AnimatedPoint::Constant { value: centre() }
283}
284
285/// A constant scale of 1 on both axes.
286pub(crate) const fn unit_scale() -> Animated<Vec2> {
287    Animated::Constant {
288        value: Vec2::new(1.0, 1.0),
289    }
290}
291
292const fn no_rotation() -> Animated<f32> {
293    Animated::Constant { value: 0.0 }
294}
295
296/// A constant opacity of 1.
297pub(crate) const fn opaque() -> Animated<f32> {
298    Animated::Constant { value: 1.0 }
299}
300
301/// Calls `visit` with `at` and the times of `animated`'s keyframes, when it
302/// has them.
303pub(crate) fn visit_keyed<T>(
304    at: &str,
305    animated: &Animated<T>,
306    visit: &mut impl FnMut(&str, &[alpha2_time::MediaTime]),
307) {
308    if let Animated::Keyframes { keyframes } = animated {
309        let times = keyframes
310            .iter()
311            .map(|keyframe| keyframe.time)
312            .collect::<Vec<_>>();
313        visit(at, &times);
314    }
315}
316
317/// [`visit_keyed`] for a point along a spatial curve.
318pub(crate) fn visit_keyed_point(
319    at: &str,
320    animated: &AnimatedPoint,
321    visit: &mut impl FnMut(&str, &[alpha2_time::MediaTime]),
322) {
323    if let AnimatedPoint::Keyframes { keyframes } = animated {
324        let times = keyframes
325            .iter()
326            .map(|keyframe| keyframe.time)
327            .collect::<Vec<_>>();
328        visit(at, &times);
329    }
330}
331
332impl VisualProperties {
333    pub(crate) fn validate(&self) -> bool {
334        self.anchor.is_finite()
335            && valid_point_animation(&self.position)
336            && valid_vec2_animation(&self.scale)
337            && valid_scalar_animation(&self.rotation_degrees, false)
338            && valid_scalar_animation(&self.opacity, true)
339    }
340}
341
342fn valid_vec2_animation(animation: &Animated<Vec2>) -> bool {
343    match animation {
344        Animated::Constant { value } => value.is_finite(),
345        Animated::Keyframes { keyframes } => {
346            valid_keyframes(keyframes)
347                && keyframes.iter().all(|keyframe| keyframe.value.is_finite())
348        }
349    }
350}
351
352/// Valid keyframes (or a constant) whose values and tangents are finite.
353pub(crate) fn valid_point_animation(animation: &AnimatedPoint) -> bool {
354    match animation {
355        AnimatedPoint::Constant { value } => value.is_finite(),
356        AnimatedPoint::Keyframes { keyframes } => {
357            !keyframes.is_empty()
358                && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
359                && keyframes.iter().all(|keyframe| {
360                    keyframe.interpolation.is_valid()
361                        && keyframe.value.is_finite()
362                        && keyframe.out_tangent.is_finite()
363                        && keyframe.in_tangent.is_finite()
364                })
365        }
366    }
367}
368
369fn valid_scalar_animation(animation: &Animated<f32>, unit_interval: bool) -> bool {
370    let valid_value =
371        |value: f32| value.is_finite() && (!unit_interval || (0.0..=1.0).contains(&value));
372    match animation {
373        Animated::Constant { value } => valid_value(*value),
374        Animated::Keyframes { keyframes } => {
375            valid_keyframes(keyframes)
376                && keyframes.iter().all(|keyframe| valid_value(keyframe.value))
377        }
378    }
379}
380
381/// Valid keyframes (or a constant) whose every finite value passes `check`.
382pub(crate) fn valid_scalar_values(animation: &Animated<f32>, check: impl Fn(f32) -> bool) -> bool {
383    let valid = |value: f32| value.is_finite() && check(value);
384    match animation {
385        Animated::Constant { value } => valid(*value),
386        Animated::Keyframes { keyframes } => {
387            valid_keyframes(keyframes) && keyframes.iter().all(|keyframe| valid(keyframe.value))
388        }
389    }
390}
391
392/// A non-empty, strictly ascending sequence whose interpolations are usable.
393pub(crate) fn valid_keyframes<T>(keyframes: &[Keyframe<T>]) -> bool {
394    !keyframes.is_empty()
395        && keyframes.windows(2).all(|pair| pair[0].time < pair[1].time)
396        && keyframes
397            .iter()
398            .all(|keyframe| keyframe.interpolation.is_valid())
399}