1use glam::dvec3;
2use ranim::{
3 animation::transform::TransformAnimSchedule,
4 color::{HueDirection, palettes::manim},
5 components::Anchor,
6 items::{group::Group, vitem::Rectangle},
7 prelude::*,
8 utils::rate_functions::{ease_in_quad, ease_out_quad, linear},
9};
10
11fn solve_hanoi(
12 n: usize,
13 idx_src: usize,
14 idx_dst: usize,
15 idx_tmp: usize,
16 move_disk: &mut impl FnMut(usize, usize),
17) {
18 if n == 1 {
19 move_disk(idx_src, idx_dst);
20 } else {
21 solve_hanoi(n - 1, idx_src, idx_tmp, idx_dst, move_disk);
22 move_disk(idx_src, idx_dst);
23 solve_hanoi(n - 1, idx_tmp, idx_dst, idx_src, move_disk);
24 }
25}
26
27#[scene]
28struct HanoiScene(pub usize);
29
30impl TimelineConstructor for HanoiScene {
31 fn construct<'t: 'r, 'r>(
32 self,
33 timeline: &'t RanimTimeline,
34 _camera: &'r mut Rabject<'t, CameraFrame>,
35 ) {
36 let n = self.0;
37 let total_sec = 10.0;
38 let rod_width = 0.4;
39 let rod_height = 5.0;
40 let rod_section_width = 4.0;
41
42 let _rods = timeline.insert([-1, 0, 1].into_iter().map(|i| {
43 let mut rod = Rectangle(rod_width, rod_height).build();
44 rod.set_color(manim::GREY_C).put_anchor_on(
45 Anchor::edge(0, -1, 0),
46 dvec3(i as f64 * rod_section_width, -4.0, 0.0),
47 );
48 rod
49 }));
50
51 let min_disk_width = rod_width * 1.7;
52 let max_disk_width = rod_section_width * 0.8;
53 let disk_height = (rod_height * 0.8) / n as f64;
54 let _disks = timeline.insert((0..n).map(|i| {
55 let factor = i as f64 / (n - 1) as f64;
56 let disk_width = min_disk_width + (max_disk_width - min_disk_width) * (1.0 - factor);
57 let mut disk = Rectangle(disk_width, disk_height).build();
58 let color = manim::RED_D.lerp(manim::BLUE_D, factor as f32, HueDirection::Increasing);
59 disk.set_color(color).put_anchor_on(
60 Anchor::edge(0, -1, 0),
61 dvec3(-rod_section_width, -4.0 + disk_height * i as f64, 0.0),
62 );
63 disk
64 }));
65
66 let mut disks = [_disks, Group::new(), Group::new()];
67
68 let anim_duration = total_sec / (2.0f64.powi(n as i32) - 1.0) / 3.0;
69 let mut move_disk = |idx_src: usize, idx_dst: usize| {
70 let top_disk_y = |idx: usize| disks[idx].len() as f64 * disk_height - 4.0;
71 let top_src = top_disk_y(idx_src) - disk_height;
72 let top_dst = top_disk_y(idx_dst);
73 let mut disk = disks[idx_src].pop().unwrap();
74
75 timeline.play(
76 disk.transform(|data| {
77 data.shift(dvec3(0.0, 3.0 - top_src, 0.0));
78 })
79 .with_duration(anim_duration)
80 .with_rate_func(ease_in_quad)
81 .apply(),
82 );
83 timeline.play(
84 disk.transform(|data| {
85 data.shift(dvec3(
86 (idx_dst as f64 - idx_src as f64) * rod_section_width,
87 0.0,
88 0.0,
89 ));
90 })
91 .with_duration(anim_duration)
92 .with_rate_func(linear)
93 .apply(),
94 );
95 timeline.play(
96 disk.transform(|data| {
97 data.shift(dvec3(0.0, top_dst - 3.0, 0.0));
98 })
99 .with_duration(anim_duration)
100 .with_rate_func(ease_out_quad)
101 .apply(),
102 );
103 timeline.sync();
104 disks[idx_dst].push(disk);
105 };
106
107 solve_hanoi(n, 0, 1, 2, &mut move_disk);
108 }
109}
110
111fn main() {
112 #[cfg(feature = "app")]
113 run_scene_app(HanoiScene(10));
114 #[cfg(not(feature = "app"))]
115 {
116 render_scene(
117 HanoiScene(5),
118 &AppOptions {
119 output_filename: "output-5.mp4",
120 ..Default::default()
121 },
122 );
123 render_scene_at_sec(HanoiScene(5), 0.0, "preview-5.png", &AppOptions::default());
124 render_scene(
125 HanoiScene(10),
126 &AppOptions {
127 output_filename: "output-10.mp4",
128 ..Default::default()
129 },
130 );
131 render_scene_at_sec(
132 HanoiScene(10),
133 0.0,
134 "preview-10.png",
135 &AppOptions::default(),
136 );
137 }
138}
139