1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use std::{
    f32::consts::PI,
    io::{stdout, Result},
    thread::sleep,
    time::{Duration, SystemTime},
};

use crossterm::{
    cursor,
    event::{self, KeyCode},
    execute, terminal,
};
use firework_rs::{
    fireworks::{ExplosionForm, Firework, FireworkConfig, FireworkManager},
    particle::ParticleConfig,
    term::Terminal,
    utils::gen_points_fan,
};
use glam::Vec2;
use rand::{seq::IteratorRandom, thread_rng, Rng};

fn main() -> Result<()> {
    let mut stdout = stdout();
    let (_width, _height) = terminal::size()?;
    let mut is_running = true;

    terminal::enable_raw_mode()?;
    execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?;

    let mut time = SystemTime::now();
    let mut term = Terminal::default();
    let mut fm = FireworkManager::default().add_firework(gen_heart_firework(Vec2::new(
        _width as f32 / 4.,
        _height as f32 / 2.,
    )));

    while is_running {
        if event::poll(Duration::ZERO)? {
            match event::read()? {
                event::Event::Key(e) => {
                    if e.code == KeyCode::Esc {
                        is_running = false;
                    }
                }
                event::Event::Resize(_, _) => {
                    fm.reset();
                    term.reinit();
                }
                _ => {}
            };
        }

        let delta_time = SystemTime::now().duration_since(time).unwrap();
        fm.update(time, delta_time);
        time = SystemTime::now();

        term.render(&fm);
        term.print(&mut stdout);

        if delta_time < Duration::from_secs_f32(0.05) {
            let rem = Duration::from_secs_f32(0.05) - delta_time;
            sleep(rem);
        }
    }

    execute!(stdout, cursor::Show, terminal::LeaveAlternateScreen)?;
    terminal::disable_raw_mode()?;

    Ok(())
}

fn gen_heart_firework(center: Vec2) -> Firework {
    let colors = vec![
        (233, 232, 237),
        (254, 142, 130),
        (200, 27, 72),
        (86, 18, 31),
    ];
    let mut particles = Vec::new();
    let trail_length = thread_rng().gen_range(100..105);
    let life_time = Duration::from_secs_f32(thread_rng().gen_range(3.0..3.2));
    let init_pos = center - Vec2::NEG_Y * 15.;
    for v in gen_points_fan(300., 45, 0.2 * PI, 0.3 * PI).iter() {
        particles.push(ParticleConfig::new(
            init_pos,
            *v,
            trail_length,
            life_time,
            *colors.iter().choose(&mut thread_rng()).unwrap(),
        ));
    }
    for v in gen_points_fan(300., 45, 0.7 * PI, 0.8 * PI).iter() {
        particles.push(ParticleConfig::new(
            init_pos,
            *v,
            trail_length,
            life_time,
            *colors.iter().choose(&mut thread_rng()).unwrap(),
        ));
    }
    let mut config = FireworkConfig::default()
        .with_ar_scale(0.1)
        .with_gravity_scale(0.1)
        .with_gradient_scale(gradient)
        .with_additional_force(move |particle| (center - particle.pos) * 2.);
    config.set_enable_gradient(true);
    Firework {
        init_time: SystemTime::now(),
        spawn_after: Duration::ZERO,
        center,
        particles,
        config,
        form: ExplosionForm::Instant { used: false },
        ..Default::default()
    }
}

fn gradient(x: f32) -> f32 {
    if x < 0.8125 {
        -0.4 * x + 1.1
    } else {
        -2. * x + 2.2
    }
}