use super::Animation;
use crate::render::Canvas;
use rand::RngExt;
struct Snowflake {
x: f64,
y: f64,
speed: f64,
wobble_phase: f64,
wobble_amp: f64,
size: f64,
}
pub struct Snow {
width: usize,
height: usize,
flakes: Vec<Snowflake>,
accumulation: Vec<f64>, rng: rand::rngs::ThreadRng,
}
impl Snow {
pub fn new(width: usize, height: usize, scale: f64) -> Self {
let mut rng = rand::rng();
let num_flakes = ((width * height) as f64 / 100.0 * scale) as usize;
let flakes = (0..num_flakes)
.map(|_| Snowflake {
x: rng.random_range(0.0..width as f64),
y: rng.random_range(-(height as f64)..height as f64),
speed: rng.random_range(5.0..15.0),
wobble_phase: rng.random_range(0.0..std::f64::consts::TAU),
wobble_amp: rng.random_range(0.5..2.0),
size: rng.random_range(0.5..1.5),
})
.collect();
Snow {
width,
height,
flakes,
accumulation: vec![0.0; width],
rng: rand::rng(),
}
}
}
impl Animation for Snow {
fn name(&self) -> &str {
"snow"
}
fn on_resize(&mut self, width: usize, height: usize) {
self.width = width;
self.height = height;
self.accumulation.resize(width, 0.0);
}
fn update(&mut self, canvas: &mut Canvas, dt: f64, time: f64) {
canvas.clear();
let wind = (time * 0.3).sin() * 3.0;
for flake in &mut self.flakes {
flake.y += flake.speed * dt;
flake.x += (wind + (time * 2.0 + flake.wobble_phase).sin() * flake.wobble_amp) * dt;
if flake.x < 0.0 {
flake.x += self.width as f64;
}
if flake.x >= self.width as f64 {
flake.x -= self.width as f64;
}
let col = (flake.x as usize).min(self.width.saturating_sub(1));
let ground_level = self.height as f64 - self.accumulation[col];
if flake.y >= ground_level - 1.0 {
self.accumulation[col] += flake.size * 0.3;
if col > 0 && self.accumulation[col] > self.accumulation[col - 1] + 2.0 {
self.accumulation[col] -= 0.5;
self.accumulation[col - 1] += 0.5;
}
if col + 1 < self.width && self.accumulation[col] > self.accumulation[col + 1] + 2.0
{
self.accumulation[col] -= 0.5;
self.accumulation[col + 1] += 0.5;
}
if self.accumulation[col] > self.height as f64 * 0.6 {
self.accumulation[col] = self.height as f64 * 0.6;
}
flake.y = self.rng.random_range(-(self.height as f64 * 0.3)..0.0);
flake.x = self.rng.random_range(0.0..self.width as f64);
flake.speed = self.rng.random_range(5.0..15.0);
continue;
}
let ix = flake.x as usize;
let iy = flake.y as usize;
if ix < canvas.width && iy < canvas.height {
let brightness = 0.7 + flake.size * 0.2;
canvas.set_colored(ix, iy, brightness, 230, 235, 255);
}
}
for x in 0..self.width.min(canvas.width) {
let snow_height = self.accumulation[x] as usize;
let ground = self.height;
for dy in 0..snow_height {
let y = ground.saturating_sub(1 + dy);
if y < canvas.height {
let depth = dy as f64 / snow_height as f64;
let r = (220.0 + 35.0 * (1.0 - depth)) as u8;
let g = (225.0 + 30.0 * (1.0 - depth)) as u8;
canvas.set_colored(x, y, 0.8 + 0.2 * (1.0 - depth), r, g, 255);
}
}
}
}
}