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(self, timeline: &RanimTimeline, _camera: &mut Rabject<CameraFrame>) {
32 let n = self.0;
33 let total_sec = 10.0;
34 let rod_width = 0.4;
35 let rod_height = 5.0;
36 let rod_section_width = 4.0;
37
38 let _rods = timeline.insert([-1, 0, 1].into_iter().map(|i| {
39 let mut rod = Rectangle(rod_width, rod_height).build();
40 rod.set_color(manim::GREY_C).put_anchor_on(
41 Anchor::edge(0, -1, 0),
42 dvec3(i as f64 * rod_section_width, -4.0, 0.0),
43 );
44 rod
45 }));
46
47 let min_disk_width = rod_width * 1.7;
48 let max_disk_width = rod_section_width * 0.8;
49 let disk_height = (rod_height * 0.8) / n as f64;
50 let _disks = timeline.insert((0..n).map(|i| {
51 let factor = i as f64 / (n - 1) as f64;
52 let disk_width = min_disk_width + (max_disk_width - min_disk_width) * (1.0 - factor);
53 let mut disk = Rectangle(disk_width, disk_height).build();
54 let color = manim::RED_D.lerp(manim::BLUE_D, factor as f32, HueDirection::Increasing);
55 disk.set_color(color).set_stroke_width(0.0).put_anchor_on(
56 Anchor::edge(0, -1, 0),
57 dvec3(-rod_section_width, -4.0 + disk_height * i as f64, 0.0),
58 );
59 disk
60 }));
61
62 let mut disks = [_disks, Group::new(), Group::new()];
63
64 let anim_duration = total_sec / (2.0f64.powi(n as i32) - 1.0) / 3.0;
65 let mut move_disk = |idx_src: usize, idx_dst: usize| {
66 let top_disk_y = |idx: usize| disks[idx].len() as f64 * disk_height - 4.0;
67 let top_src = top_disk_y(idx_src) - disk_height;
68 let top_dst = top_disk_y(idx_dst);
69 let mut disk = disks[idx_src].pop().unwrap();
70
71 timeline.play(
72 disk.transform(|data| {
73 data.shift(dvec3(0.0, 3.0 - top_src, 0.0));
74 })
75 .with_duration(anim_duration)
76 .with_rate_func(ease_in_quad)
77 .apply(),
78 );
79 timeline.play(
80 disk.transform(|data| {
81 data.shift(dvec3(
82 (idx_dst as f64 - idx_src as f64) * rod_section_width,
83 0.0,
84 0.0,
85 ));
86 })
87 .with_duration(anim_duration)
88 .with_rate_func(linear)
89 .apply(),
90 );
91 timeline.play(
92 disk.transform(|data| {
93 data.shift(dvec3(0.0, top_dst - 3.0, 0.0));
94 })
95 .with_duration(anim_duration)
96 .with_rate_func(ease_out_quad)
97 .apply(),
98 );
99 timeline.sync();
100 disks[idx_dst].push(disk);
101 };
102
103 solve_hanoi(n, 0, 1, 2, &mut move_disk);
104 }
105}
106
107fn main() {
108 #[cfg(feature = "app")]
109 run_scene_app(HanoiScene(10));
110 #[cfg(not(feature = "app"))]
111 {
112 render_scene(
113 HanoiScene(5),
114 &AppOptions {
115 output_filename: "output-5.mp4",
116 ..Default::default()
117 },
118 );
119 render_scene_at_sec(HanoiScene(5), 0.0, "preview-5.png", &AppOptions::default());
120 render_scene(
121 HanoiScene(10),
122 &AppOptions {
123 output_filename: "output-10.mp4",
124 ..Default::default()
125 },
126 );
127 render_scene_at_sec(
128 HanoiScene(10),
129 0.0,
130 "preview-10.png",
131 &AppOptions::default(),
132 );
133 }
134}
135