1 | use glam::dvec3;
|
2 | use 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 |
|
11 | fn 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]
|
28 | struct HanoiScene(pub usize);
|
29 |
|
30 | impl 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 |
|
111 | fn 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 |
|