1#[cfg(not(feature = "std"))]
7use crate::math::F32Ext as _;
8
9pub const NORMALIZED_MAX: i32 = 65_535;
11
12pub const FRAME_INTERVAL_MS: u32 = 33;
14
15pub const DEFAULT_DURATION_MS: u32 = 250;
17
18pub const PORT_HOLE_DURATION_MS: u32 = 6 * FRAME_INTERVAL_MS;
20
21pub const SHUTTER_DURATION_MS: u32 = 6 * FRAME_INTERVAL_MS;
23
24pub const MOOOK_DURATION_MS: u32 =
26 (MOOOK_IN.len() as u32 + MOOOK_OUT.len() as u32) * FRAME_INTERVAL_MS;
27
28const MOOOK_IN: [i32; 3] = [0, 1, 20];
29const MOOOK_OUT: [i32; 4] = [4, 2, 1, 0];
30
31#[inline]
33pub fn timing_scaled(time_normalized: i32, interval_start: i32, interval_end: i32) -> i32 {
34 if interval_end == interval_start {
35 return NORMALIZED_MAX;
36 }
37 let result = time_normalized - interval_start;
38 (result * NORMALIZED_MAX) / (interval_end - interval_start)
39}
40
41#[inline]
43pub fn timing_clip(progress: i32) -> i32 {
44 progress.clamp(0, NORMALIZED_MAX)
45}
46
47#[inline]
49pub fn timing_half_phase(progress: f32) -> (f32, bool) {
50 if progress < 0.5 {
51 (progress * 2.0, true)
52 } else {
53 ((progress - 0.5) * 2.0, false)
54 }
55}
56
57#[inline]
59pub fn timing_shutter_phase(progress: f32) -> (f32, bool) {
60 const FIRST: f32 = 2.0 / 6.0;
61 if progress < FIRST {
62 (progress / FIRST, true)
63 } else {
64 ((progress - FIRST) / (1.0 - FIRST), false)
65 }
66}
67
68#[inline]
69pub fn moook_in_duration_ms() -> u32 {
70 MOOOK_IN.len() as u32 * FRAME_INTERVAL_MS
71}
72
73#[inline]
74pub fn moook_out_duration_ms() -> u32 {
75 MOOOK_OUT.len() as u32 * FRAME_INTERVAL_MS
76}
77
78#[inline]
79pub fn moook_duration_ms() -> u32 {
80 moook_in_duration_ms() + moook_out_duration_ms()
81}
82
83#[inline]
84pub fn moook_soft_duration_ms(mid_frames: i32) -> u32 {
85 moook_duration_ms() + mid_frames.max(0) as u32 * FRAME_INTERVAL_MS
86}
87
88fn interpolate_linear(normalized: i32, from: i64, to: i64) -> i64 {
89 from + (normalized as i64 * (to - from)) / NORMALIZED_MAX as i64
90}
91
92fn interpolate_moook_frames(
93 normalized: i32,
94 from: i64,
95 to: i64,
96 frames_in: &[i32],
97 frames_out: &[i32],
98 mid_frames: i32,
99 bounce_back: bool,
100) -> i64 {
101 let direction = if from == to {
102 0
103 } else if from < to {
104 1
105 } else {
106 -1
107 };
108 if direction == 0 {
109 return from;
110 }
111 let direction_out = if bounce_back { direction } else { -direction };
112 let num_in = frames_in.len() as i32;
113 let num_out = frames_out.len() as i32;
114 let num_total = num_in + mid_frames + num_out;
115 if num_total <= 0 {
116 return if normalized >= NORMALIZED_MAX {
117 to
118 } else {
119 from
120 };
121 }
122
123 let mut frame_idx = ((normalized as i64 * num_total as i64
124 + (NORMALIZED_MAX as i64 / (2 * num_total as i64)))
125 / NORMALIZED_MAX as i64) as i32;
126 frame_idx = frame_idx.clamp(0, num_total - 1);
127
128 if normalized >= NORMALIZED_MAX {
129 return to;
130 }
131 if frame_idx < 0 {
132 return from;
133 }
134 if frame_idx < num_in {
135 return from + direction as i64 * frames_in[frame_idx as usize] as i64;
136 }
137 if frame_idx < num_in + mid_frames && mid_frames > 0 {
138 let shifted =
139 normalized - ((num_in as i64 * NORMALIZED_MAX as i64) / num_total as i64) as i32;
140 let mid_normalized = ((num_total as i64 * shifted as i64) / mid_frames as i64) as i32;
141 let from_mid = from + direction as i64 * frames_in[(num_in - 1) as usize] as i64;
142 let to_mid = to + direction_out as i64 * frames_out[0] as i64;
143 return interpolate_linear(mid_normalized, from_mid, to_mid);
144 }
145 let out_idx = frame_idx - num_in - mid_frames;
146 to + direction_out as i64 * frames_out[out_idx as usize] as i64
147}
148
149pub fn interpolate_moook(normalized: i32, from: i64, to: i64) -> i64 {
151 interpolate_moook_frames(normalized, from, to, &MOOOK_IN, &MOOOK_OUT, 0, true)
152}
153
154pub fn interpolate_moook_soft(normalized: i32, from: i64, to: i64, mid_frames: i32) -> i64 {
156 interpolate_moook_frames(
157 normalized, from, to, &MOOOK_IN, &MOOOK_OUT, mid_frames, true,
158 )
159}
160
161pub fn moook_curve(t: f32) -> f32 {
163 let normalized = (t.clamp(0.0, 1.0) * NORMALIZED_MAX as f32).round() as i32;
164 let v = interpolate_moook(normalized, 0, NORMALIZED_MAX as i64);
165 v as f32 / NORMALIZED_MAX as f32
166}
167
168pub fn table_ease_in_sample(t: f32) -> f32 {
170 const TABLE: [u16; 33] = [
171 0, 64, 256, 576, 1024, 1600, 2304, 3136, 4096, 5184, 6400, 7744, 9216, 10816, 12544, 14400,
172 16384, 18496, 20736, 23104, 25600, 28224, 30976, 33856, 36864, 40000, 43264, 46656, 50176,
173 53824, 57600, 61504, 65535,
174 ];
175 ease_table_sample(t, &TABLE)
176}
177
178fn ease_table_sample(t: f32, table: &[u16]) -> f32 {
179 if table.is_empty() {
180 return t;
181 }
182 let progress = (t.clamp(0.0, 1.0) * NORMALIZED_MAX as f32).round() as i32;
183 if progress <= 0 {
184 return 0.0;
185 }
186 if progress >= NORMALIZED_MAX {
187 return 1.0;
188 }
189 let max_entry = table.len() - 1;
190 let stride = NORMALIZED_MAX / max_entry as i32;
191 let index = (progress * max_entry as i32) / NORMALIZED_MAX;
192 let from = table[index as usize] as i64;
193 let delta = table[(index + 1) as usize] as i64 - from;
194 let v = from + (delta * (progress - index * stride) as i64) / stride as i64;
195 v as f32 / NORMALIZED_MAX as f32
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn moook_reaches_endpoints() {
204 assert_eq!(interpolate_moook(0, 0, 100), 0);
205 assert_eq!(interpolate_moook(NORMALIZED_MAX, 0, 100), 100);
206 }
207
208 #[test]
209 fn timing_scaled_maps_interval() {
210 let mid = timing_scaled(NORMALIZED_MAX / 2, 0, NORMALIZED_MAX);
211 assert!((mid - NORMALIZED_MAX / 2).abs() <= 1);
212 }
213
214 #[test]
215 fn moook_curve_is_monotonic_overall() {
216 let a = moook_curve(0.0);
217 let b = moook_curve(1.0);
218 assert!((a - 0.0).abs() < 0.01);
219 assert!((b - 1.0).abs() < 0.01);
220 }
221}