Preview App hanoi 示例输出 hanoi 示例输出
use ranim::{
    anims::morph::MorphAnim,
    color::{HueDirection, palettes::manim},
    glam::dvec3,
    items::vitem::{VItem, geometry::Rectangle},
    prelude::*,
    utils::rate_functions::{ease_in_quad, ease_out_quad, linear},
};

fn solve_hanoi(
    n: usize,
    idx_src: usize,
    idx_dst: usize,
    idx_tmp: usize,
    move_disk: &mut impl FnMut(usize, usize),
) {
    if n == 1 {
        move_disk(idx_src, idx_dst);
    } else {
        solve_hanoi(n - 1, idx_src, idx_tmp, idx_dst, move_disk);
        move_disk(idx_src, idx_dst);
        solve_hanoi(n - 1, idx_tmp, idx_dst, idx_src, move_disk);
    }
}

fn hanoi(r: &mut RanimScene, n: usize) {
    let total_sec = 10.0;
    let rod_width = 0.4;
    let rod_height = 5.0;
    let rod_section_width = 4.0;

    let rods = [-1, 0, 1]
        .into_iter()
        .map(|i: i32| {
            let mut rect: VItem = Rectangle::new(rod_width, rod_height)
                .with(|rect| {
                    rect.set_color(manim::GREY_C);
                })
                .into();
            rect.move_anchor_to(
                AabbPoint(dvec3(0.0, -1.0, 0.0)),
                dvec3(i as f64 * rod_section_width, -4.0, 0.0),
            );
            rect
        })
        .collect::<Vec<_>>();

    let min_disk_width = rod_width * 1.7;
    let max_disk_width = rod_section_width * 0.8;
    let disk_height = (rod_height * 0.8) / n as f64;
    let disks = (0..n)
        .map(|i| {
            let factor = i as f64 / (n - 1) as f64;
            let disk_width = min_disk_width + (max_disk_width - min_disk_width) * (1.0 - factor);
            let mut disk: VItem = Rectangle::new(disk_width, disk_height)
                .with(|rect| {
                    let color =
                        manim::RED_D.lerp(manim::BLUE_D, factor as f32, HueDirection::Increasing);
                    rect.stroke_width = 0.0;
                    rect.set_color(color);
                })
                .into();
            disk.move_anchor_to(
                AabbPoint(dvec3(0.0, -1.0, 0.0)),
                dvec3(-rod_section_width, -4.0 + disk_height * i as f64, 0.001),
            );
            let sequence = seq![disk.show()];
            (sequence, disk)
        })
        .collect::<Vec<_>>();

    let mut disks_by_rod = [disks, Vec::new(), Vec::new()];

    let anim_duration = total_sec / (2.0f64.powi(n as i32) - 1.0) / 3.0;
    let mut move_disk = |idx_src: usize, idx_dst: usize| {
        let top_disk_y = |idx: usize| disks_by_rod[idx].len() as f64 * disk_height - 4.0;
        let top_src = top_disk_y(idx_src) - disk_height;
        let top_dst = top_disk_y(idx_dst);
        let mut disk_entry = disks_by_rod[idx_src].pop().unwrap();

        {
            let (sequence, disk) = &mut disk_entry;
            sequence.push(
                disk.morph(|data| {
                    data.shift(dvec3(0.0, 3.0 - top_src, 0.0));
                })
                .with_duration(anim_duration)
                .with_rate_func(ease_in_quad),
            );
            sequence.push(
                disk.morph(|data| {
                    data.shift(dvec3(
                        (idx_dst as f64 - idx_src as f64) * rod_section_width,
                        0.0,
                        0.0,
                    ));
                })
                .with_duration(anim_duration)
                .with_rate_func(linear),
            );
            sequence.push(
                disk.morph(|data| {
                    data.shift(dvec3(0.0, top_dst - 3.0, 0.0));
                })
                .with_duration(anim_duration)
                .with_rate_func(ease_out_quad),
            );
        }
        let target = disk_entry.0.cursor_sec();
        for rod in &mut disks_by_rod {
            for (sequence, _) in rod {
                sequence.hold_to(target);
            }
        }
        disks_by_rod[idx_dst].push(disk_entry);
    };

    solve_hanoi(n, 0, 1, 2, &mut move_disk);
    r.insert_time_mark(0.0, TimeMark::Capture(format!("preview-{n}.png")));

    let mut content = AnimStack::new();
    for rod in disks_by_rod {
        for (sequence, _) in rod {
            content.push(sequence);
        }
    }
    let total_secs = content.duration_secs();
    r.play(CameraFrame::default().show().with_duration(total_secs));
    r.play(stack![content, rods.show().with_duration(total_secs)]);
}

#[scene]
#[wasm_demo_doc]
#[output(dir = "./output/hanoi")]
fn hanoi_5(r: &mut RanimScene) {
    hanoi(r, 5);
}

#[scene(name = "hanoi")]
#[wasm_demo_doc]
#[output(dir = "./output/hanoi")]
fn hanoi_10(r: &mut RanimScene) {
    hanoi(r, 10);
}