1use std::f32::consts::PI;
6use std::hash::Hash;
7use std::ops::{Deref, Range};
8
9use fastanim_diff::{Algorithm, DiffOptions, Differ, Op, expand};
10use kurbo::{Affine, Point, Vec2};
11
12use crate::Interpolate;
13use crate::anim::{Animation, RateFn};
14use crate::color::{BLUE, Color, GREEN, GREY, ORANGE, RED, YELLOW};
15use crate::geom::align;
16use crate::mobject::{MobjectId, SceneState, VState};
17use crate::timeline::Scene;
18
19#[derive(Debug, Clone, PartialEq)]
22pub struct Group<K> {
23 pub ids: Vec<MobjectId>,
25 pub parts: Vec<(K, Range<usize>)>,
27 pub lines: Vec<Range<usize>>,
30}
31
32#[derive(Debug, Clone, PartialEq)]
35pub struct Layout<K> {
36 pub states: Vec<VState>,
38 pub parts: Vec<(K, Range<usize>)>,
40 pub lines: Vec<Range<usize>>,
42}
43
44impl<K: Clone> Group<K> {
45 pub fn add(s: &mut Scene, layout: &Layout<K>) -> Self {
47 Self {
48 ids: layout.states.iter().map(|m| s.add(m.clone())).collect(),
49 parts: layout.parts.clone(),
50 lines: layout.lines.clone(),
51 }
52 }
53}
54
55impl<K> Deref for Group<K> {
56 type Target = [MobjectId];
57 fn deref(&self) -> &[MobjectId] {
58 &self.ids
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub enum Phasing {
65 Sequential,
67 #[default]
69 Overlapped,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
74pub enum ReplaceStyle {
75 #[default]
77 Morph,
78 CrossFade,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq)]
84pub struct DiffStyle {
85 pub phasing: Phasing,
87 pub lag_ratio: f32,
89 pub move_arc: f64,
91 pub replace: ReplaceStyle,
93 pub highlight_changes: bool,
95 pub debug: bool,
98}
99
100impl Default for DiffStyle {
101 fn default() -> Self {
102 Self {
103 phasing: Phasing::Overlapped,
104 lag_ratio: 0.05,
105 move_arc: std::f64::consts::FRAC_PI_3,
106 replace: ReplaceStyle::Morph,
107 highlight_changes: false,
108 debug: false,
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115enum Kind {
116 Delete,
117 Equal,
118 Move,
119 Replace,
120 Insert,
121}
122
123impl Kind {
124 fn phase(self, phasing: Phasing) -> (f32, f32) {
126 match (phasing, self) {
127 (Phasing::Overlapped, Kind::Delete) => (0.0, 0.35),
128 (Phasing::Overlapped, Kind::Equal | Kind::Move) => (0.2, 0.8),
129 (Phasing::Overlapped, Kind::Replace) => (0.3, 0.85),
130 (Phasing::Overlapped, Kind::Insert) => (0.6, 1.0),
131 (Phasing::Sequential, Kind::Delete) => (0.0, 1.0 / 3.0),
132 (Phasing::Sequential, Kind::Equal | Kind::Move | Kind::Replace) => {
133 (1.0 / 3.0, 2.0 / 3.0)
134 }
135 (Phasing::Sequential, Kind::Insert) => (2.0 / 3.0, 1.0),
136 }
137 }
138
139 fn debug_color(self) -> Color {
140 match self {
141 Kind::Delete => RED,
142 Kind::Equal => GREY,
143 Kind::Move => BLUE,
144 Kind::Replace => ORANGE,
145 Kind::Insert => GREEN,
146 }
147 }
148}
149
150struct Track {
151 id: MobjectId,
152 kind: Kind,
153 target: Option<VState>,
155 ends: Option<(VState, VState)>,
157 window: (f32, f32),
159}
160
161pub struct TransformDiff {
164 tracks: Vec<Track>,
165 ops: Vec<Op>,
166 style: DiffStyle,
167}
168
169impl<K: Eq + Hash + Clone> Group<K> {
170 pub fn transform_diff<C: Eq + Hash>(
177 &mut self,
178 s: &mut Scene,
179 to: &Layout<K>,
180 class: impl Fn(&K) -> Option<C>,
181 ) -> TransformDiff {
182 let ops = self.diff(s.state(), to, class);
183 self.transform_ops(s, to, ops)
184 }
185
186 pub fn diff<C: Eq + Hash>(
193 &self,
194 state: &SceneState,
195 to: &Layout<K>,
196 class: impl Fn(&K) -> Option<C>,
197 ) -> Vec<Op> {
198 let ca: Vec<Point> = (self.parts.iter())
199 .map(|(_, r)| center(self.ids[r.clone()].iter().map(|id| &state[id])))
200 .collect();
201 let cb: Vec<Point> = (to.parts.iter())
202 .map(|(_, r)| center(to.states[r.clone()].iter()))
203 .collect();
204 #[allow(clippy::single_range_in_vec_init, reason = "one line of all parts")]
205 let lines = |l: &[Range<usize>], n: usize| match l {
206 [] => vec![0..n],
207 l => l.to_vec(),
208 };
209 let (la, lb) = (
210 lines(&self.lines, self.parts.len()),
211 lines(&to.lines, to.parts.len()),
212 );
213 let keys = |parts: &[(K, Range<usize>)], l: &[Range<usize>]| -> Vec<Vec<K>> {
214 (l.iter())
215 .map(|r| parts[r.clone()].iter().map(|(k, _)| k.clone()).collect())
216 .collect()
217 };
218 let (ka, kb) = (keys(&self.parts, &la), keys(&to.parts, &lb));
219 let outer = Differ::new(&ka, &kb, |k| k.clone())
220 .options(DiffOptions {
221 algorithm: Algorithm::Patience,
222 ..DiffOptions::default()
223 })
224 .run();
225 expand(&outer, &la, &lb, |ra, rb| {
226 let (ao, bo) = (ra.start, rb.start);
227 Differ::new(&self.parts[ra], &to.parts[rb], |(k, _)| k.clone())
228 .cost(|i, j| ca[ao + i].distance(cb[bo + j]))
229 .class(|(k, _)| class(k))
230 .run()
231 })
232 }
233
234 pub fn transform_ops(&mut self, s: &mut Scene, to: &Layout<K>, ops: Vec<Op>) -> TransformDiff {
237 let glyphs = |parts: &[(K, Range<usize>)], r: Range<usize>| -> Vec<usize> {
238 parts[r].iter().flat_map(|(_, g)| g.clone()).collect()
239 };
240 let mut tracks = Vec::new();
241 let mut new_ids = vec![None; to.states.len()];
242 for op in &ops {
243 let (a, b, kind) = match op {
244 Op::Equal { a, b } => (*a..a + 1, *b..b + 1, Kind::Equal),
245 Op::Move { a, b } => (*a..a + 1, *b..b + 1, Kind::Move),
246 Op::Replace { a, b } => (a.clone(), b.clone(), Kind::Replace),
247 Op::Delete { a } => (*a..a + 1, 0..0, Kind::Delete),
248 Op::Insert { b } => (0..0, *b..b + 1, Kind::Insert),
249 };
250 let (a, b) = (glyphs(&self.parts, a), glyphs(&to.parts, b));
251 for k in 0..a.len().max(b.len()) {
253 let (id, kind) = match (a.get(k), b.get(k)) {
254 (Some(&i), Some(_)) => (self.ids[i], kind),
255 (Some(&i), None) => (self.ids[i], Kind::Delete),
256 (None, Some(&j)) => (
257 s.add(VState {
258 opacity: 0.0,
259 ..to.states[j].clone()
260 }),
261 Kind::Insert,
262 ),
263 (None, None) => unreachable!(),
264 };
265 let target = b.get(k).map(|&j| {
266 new_ids[j] = Some(id);
267 to.states[j].clone()
268 });
269 tracks.push(Track {
270 id,
271 kind,
272 target,
273 ends: None,
274 window: (0.0, 1.0),
275 });
276 }
277 }
278
279 self.ids = new_ids
280 .into_iter()
281 .map(|id| id.expect("ops cover every target part"))
282 .collect();
283 self.parts = to.parts.clone();
284 self.lines = to.lines.clone();
285 TransformDiff {
286 tracks,
287 ops,
288 style: DiffStyle::default(),
289 }
290 }
291}
292
293pub(crate) fn center<'a>(states: impl Iterator<Item = &'a VState>) -> Point {
295 states
296 .filter_map(|m| m.path.bbox())
297 .reduce(|a, b| a.union(b))
298 .map_or(Point::ORIGIN, |b| b.center())
299}
300
301fn vanished(m: &VState) -> VState {
303 VState {
304 opacity: 0.0,
305 ..m.clone().scale(0.2)
306 }
307}
308
309impl TransformDiff {
310 pub fn style(self, style: DiffStyle) -> Self {
312 Self { style, ..self }
313 }
314
315 pub fn debug(mut self) -> Self {
317 self.style.debug = true;
318 self
319 }
320
321 pub fn ops(&self) -> &[Op] {
323 &self.ops
324 }
325}
326
327impl Animation for TransformDiff {
328 fn plan(&mut self, state: &SceneState) {
329 let phasing = self.style.phasing;
332 for kind in [
334 Kind::Delete,
335 Kind::Equal,
336 Kind::Move,
337 Kind::Replace,
338 Kind::Insert,
339 ] {
340 let (s, e) = kind.phase(phasing);
341 let in_phase = |t: &&mut Track| t.kind.phase(phasing) == (s, e);
342 let n = self.tracks.iter_mut().filter(in_phase).count();
343 let lag = self.style.lag_ratio;
344 let w = (e - s) / (1.0 + n.saturating_sub(1) as f32 * lag);
345 let mut k = 0.0;
346 for t in self.tracks.iter_mut().filter(in_phase) {
347 t.window = (s + k * lag * w, w);
348 k += 1.0;
349 }
350 }
351 for t in &mut self.tracks {
352 let from = state[&t.id].clone();
353 t.ends = Some(match (&t.target, t.kind) {
354 (None, _) => (from.clone(), vanished(&from)),
355 (Some(to), Kind::Insert) => (vanished(to), to.clone()),
356 (Some(to), _) => {
357 let (a, b) = align(&from.path, &to.path);
358 (
359 VState { path: a, ..from },
360 VState {
361 path: b,
362 ..to.clone()
363 },
364 )
365 }
366 });
367 }
368 }
369
370 fn sample(&self, alpha: f32, state: &mut SceneState) {
371 for t in &self.tracks {
372 if alpha >= 1.0 {
374 match &t.target {
375 Some(to) => state.insert(t.id, to.clone()),
376 None => state.remove(&t.id),
377 };
378 continue;
379 }
380 let (a, b) = t.ends.as_ref().expect("sample before plan");
381 let p = RateFn::Smooth.apply((alpha - t.window.0) / t.window.1);
382 let mut m = if t.kind == Kind::Replace && self.style.replace == ReplaceStyle::CrossFade
383 {
384 let (m, f) = if p < 0.5 {
385 (a, 1.0 - 2.0 * p)
386 } else {
387 (b, 2.0 * p - 1.0)
388 };
389 VState {
390 opacity: m.opacity * f,
391 ..m.clone()
392 }
393 } else {
394 VState::lerp(a, b, p)
395 };
396 if t.kind == Kind::Move && self.style.move_arc != 0.0 {
397 let d = b.path.center() - a.path.center();
401 let bow = Vec2::new(-d.y, d.x) * (0.5 * (self.style.move_arc / 4.0).tan());
402 let h = f64::from(4.0 * p * (1.0 - p));
403 m = m.transform(Affine::translate(bow * h));
404 }
405 if self.style.debug {
406 m.fill = t.kind.debug_color().with_alpha(m.fill.a.max(0.5));
407 } else if self.style.highlight_changes && matches!(t.kind, Kind::Insert | Kind::Replace)
408 {
409 m.fill = Color::lerp(&m.fill, &YELLOW.with_alpha(m.fill.a), 0.8 * (PI * p).sin());
410 }
411 state.insert(t.id, m);
412 }
413 }
414
415 fn rate_fn(&self) -> RateFn {
416 RateFn::Linear
418 }
419}