Skip to main content

alpha2_eval/
text_animation.rs

1//! Text animators sampled at a time: the plain numbers each animator applies.
2
3use alpha2_geometry::StableHash;
4use alpha2_model::{RangeShape, RangeUnits, RunState, TextAnimator, TextSelector, TextUnit};
5use alpha2_time::MediaTime;
6
7use crate::{sample_rgba, sample_scalar, sample_vec2};
8
9/// An animator's selector and properties at one time.
10#[derive(Clone, Debug, PartialEq)]
11pub struct SampledAnimator {
12    /// What it selects.
13    pub selector: SampledSelector,
14    /// Offset in font sizes, +y down.
15    pub offset: Option<[f32; 2]>,
16    /// Scale about each character's centre.
17    pub scale: Option<[f32; 2]>,
18    /// Clockwise degrees about each character's centre.
19    pub rotation_degrees: Option<f32>,
20    /// Target opacity.
21    pub opacity: Option<f32>,
22    /// Target fill color, straight sRGB.
23    pub fill: Option<[f32; 4]>,
24    /// Target stroke color, straight sRGB.
25    pub stroke: Option<[f32; 4]>,
26    /// Extra space after each character, font sizes.
27    pub tracking: Option<f32>,
28}
29
30/// A selector at one time.
31#[derive(Clone, Debug, PartialEq)]
32pub enum SampledSelector {
33    /// A Range selector's values.
34    Range {
35        /// What it counts.
36        based_on: TextUnit,
37        /// Percents or indices.
38        units: RangeUnits,
39        /// Start, with the offset not added.
40        start: f32,
41        /// End, with the offset not added.
42        end: f32,
43        /// Added to start and end.
44        offset: f32,
45        /// How selection rises and falls.
46        shape: RangeShape,
47        /// Selection scale.
48        amount: f32,
49    },
50    /// A Timing selector (its values are not animated).
51    Timing {
52        /// Which runs.
53        state: RunState,
54        /// Fade length.
55        ramp: MediaTime,
56        /// Sweep each run's characters in order.
57        sweep: bool,
58    },
59    /// A Class selector.
60    Class {
61        /// The class name.
62        class: String,
63    },
64}
65
66/// `animators` sampled at `time`, in order.
67#[must_use]
68pub fn sample_text_animators(animators: &[TextAnimator], time: MediaTime) -> Vec<SampledAnimator> {
69    animators
70        .iter()
71        .map(|animator| {
72            let properties = &animator.properties;
73            let pair = |value: alpha2_model::Vec2| [value.x, value.y];
74            let color =
75                |value: alpha2_model::Rgba| [value.red, value.green, value.blue, value.alpha];
76            SampledAnimator {
77                selector: match &animator.selector {
78                    TextSelector::Range {
79                        based_on,
80                        units,
81                        start,
82                        end,
83                        offset,
84                        shape,
85                        amount,
86                    } => SampledSelector::Range {
87                        based_on: *based_on,
88                        units: *units,
89                        start: sample_scalar(start, time),
90                        end: sample_scalar(end, time),
91                        offset: sample_scalar(offset, time),
92                        shape: *shape,
93                        amount: sample_scalar(amount, time).clamp(0.0, 1.0),
94                    },
95                    TextSelector::Timing { state, ramp, sweep } => SampledSelector::Timing {
96                        state: *state,
97                        ramp: *ramp,
98                        sweep: *sweep,
99                    },
100                    TextSelector::Class { class } => SampledSelector::Class {
101                        class: class.clone(),
102                    },
103                },
104                offset: properties
105                    .offset
106                    .as_ref()
107                    .map(|value| pair(sample_vec2(value, time))),
108                scale: properties
109                    .scale
110                    .as_ref()
111                    .map(|value| pair(sample_vec2(value, time)).map(|axis| axis.max(0.0))),
112                rotation_degrees: properties
113                    .rotation_degrees
114                    .as_ref()
115                    .map(|value| sample_scalar(value, time)),
116                opacity: properties
117                    .opacity
118                    .as_ref()
119                    .map(|value| sample_scalar(value, time).clamp(0.0, 1.0)),
120                fill: properties
121                    .fill
122                    .as_ref()
123                    .map(|value| color(sample_rgba(value, time))),
124                stroke: properties
125                    .stroke
126                    .as_ref()
127                    .map(|value| color(sample_rgba(value, time))),
128                tracking: properties
129                    .tracking
130                    .as_ref()
131                    .map(|value| sample_scalar(value, time)),
132            }
133        })
134        .collect()
135}
136
137/// Feeds every value of `animators` to `hash`.
138pub fn hash_sampled_animators(hash: &mut StableHash, animators: &[SampledAnimator]) {
139    hash.u64(animators.len() as u64);
140    for animator in animators {
141        match &animator.selector {
142            SampledSelector::Range {
143                based_on,
144                units,
145                start,
146                end,
147                offset,
148                shape,
149                amount,
150            } => {
151                hash.u32(0);
152                hash.u32(*based_on as u32);
153                hash.u32(*units as u32);
154                hash.u32(*shape as u32);
155                for value in [start, end, offset, amount] {
156                    hash.f32(*value);
157                }
158            }
159            SampledSelector::Timing { state, ramp, sweep } => {
160                hash.u32(1);
161                hash.u32(*state as u32);
162                hash.u64(ramp.ticks().cast_unsigned());
163                hash.u32(u32::from(*sweep));
164            }
165            SampledSelector::Class { class } => {
166                hash.u32(2);
167                hash.bytes(class.as_bytes());
168            }
169        }
170        let mut option = |values: Option<&[f32]>| match values {
171            None => hash.u32(0),
172            Some(values) => {
173                hash.u32(1);
174                for value in values {
175                    hash.f32(*value);
176                }
177            }
178        };
179        option(animator.offset.as_ref().map(<[f32; 2]>::as_slice));
180        option(animator.scale.as_ref().map(<[f32; 2]>::as_slice));
181        option(animator.rotation_degrees.as_ref().map(std::slice::from_ref));
182        option(animator.opacity.as_ref().map(std::slice::from_ref));
183        option(animator.fill.as_ref().map(<[f32; 4]>::as_slice));
184        option(animator.stroke.as_ref().map(<[f32; 4]>::as_slice));
185        option(animator.tracking.as_ref().map(std::slice::from_ref));
186    }
187}