#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct SnowOverlayConfig {
pub flake_count: usize,
pub radius_min: f32,
pub radius_max: f32,
pub drift: f32,
pub fall_speed: f32,
pub opacity: f32,
pub enabled: bool,
}
impl Default for SnowOverlayConfig {
fn default() -> Self {
Self {
flake_count: 150,
radius_min: 0.002,
radius_max: 0.008,
drift: 0.001,
fall_speed: 0.003,
opacity: 0.8,
enabled: true,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SnowFlake {
pub pos: [f32; 2],
pub radius: f32,
pub phase: f32,
pub alpha: f32,
}
fn pcg(state: &mut u64) -> f32 {
*state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
let x = ((*state >> 18) ^ *state) >> 27;
let rot = (*state >> 59) as u32;
let v = (x as u32).rotate_right(rot);
(v >> 8) as f32 / 16_777_216.0
}
#[allow(dead_code)]
pub fn generate_snowflakes(seed: u64, cfg: &SnowOverlayConfig) -> Vec<SnowFlake> {
let mut rng = seed ^ 0xD1CE_BEEF_5A1A_0001;
let r_range = cfg.radius_max - cfg.radius_min;
(0..cfg.flake_count)
.map(|_| {
let x = pcg(&mut rng);
let y = pcg(&mut rng);
let radius = cfg.radius_min + pcg(&mut rng) * r_range;
let phase = pcg(&mut rng);
let alpha = (pcg(&mut rng) * 0.4 + 0.6) * cfg.opacity;
SnowFlake {
pos: [x, y],
radius,
phase,
alpha,
}
})
.collect()
}
#[allow(dead_code)]
pub fn new_snow_overlay_config() -> SnowOverlayConfig {
SnowOverlayConfig::default()
}
#[allow(dead_code)]
pub fn so_set_count(cfg: &mut SnowOverlayConfig, count: usize) {
cfg.flake_count = count;
}
#[allow(dead_code)]
pub fn so_set_opacity(cfg: &mut SnowOverlayConfig, v: f32) {
cfg.opacity = v.clamp(0.0, 1.0);
}
#[allow(dead_code)]
pub fn so_set_fall_speed(cfg: &mut SnowOverlayConfig, v: f32) {
cfg.fall_speed = v.clamp(0.0, 1.0);
}
#[allow(dead_code)]
pub fn so_step(flakes: &mut [SnowFlake], cfg: &SnowOverlayConfig) {
for f in flakes.iter_mut() {
f.pos[1] += cfg.fall_speed;
if f.pos[1] > 1.0 {
f.pos[1] -= 1.0;
}
}
}
#[allow(dead_code)]
pub fn so_average_radius(flakes: &[SnowFlake]) -> f32 {
if flakes.is_empty() {
return 0.0;
}
flakes.iter().map(|f| f.radius).sum::<f32>() / flakes.len() as f32
}
#[allow(dead_code)]
pub fn so_to_json(cfg: &SnowOverlayConfig) -> String {
format!(
"{{\"flake_count\":{},\"opacity\":{:.3},\"enabled\":{}}}",
cfg.flake_count, cfg.opacity, cfg.enabled
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_correct_count() {
let cfg = new_snow_overlay_config();
let flakes = generate_snowflakes(42, &cfg);
assert_eq!(flakes.len(), cfg.flake_count);
}
#[test]
fn deterministic() {
let cfg = new_snow_overlay_config();
let a = generate_snowflakes(99, &cfg);
let b = generate_snowflakes(99, &cfg);
assert_eq!(a[0].pos[0].to_bits(), b[0].pos[0].to_bits());
}
#[test]
fn positions_in_range() {
let cfg = new_snow_overlay_config();
let flakes = generate_snowflakes(7, &cfg);
for f in &flakes {
assert!((0.0..=1.0).contains(&f.pos[0]));
assert!((0.0..=1.0).contains(&f.pos[1]));
}
}
#[test]
fn radius_in_config_range() {
let cfg = new_snow_overlay_config();
let flakes = generate_snowflakes(1, &cfg);
for f in &flakes {
assert!(f.radius >= cfg.radius_min && f.radius <= cfg.radius_max + 1e-5);
}
}
#[test]
fn step_advances_y() {
let cfg = new_snow_overlay_config();
let mut flakes = generate_snowflakes(3, &cfg);
let y0 = flakes[0].pos[1];
so_step(&mut flakes, &cfg);
let y1 = flakes[0].pos[1];
assert!(y1 != y0 || cfg.fall_speed < 1e-8);
}
#[test]
fn step_wraps_around() {
let cfg = new_snow_overlay_config();
let mut flakes = vec![SnowFlake {
pos: [0.5, 0.99],
radius: 0.004,
phase: 0.0,
alpha: 1.0,
}];
so_step(&mut flakes, &cfg);
assert!(flakes[0].pos[1] <= 1.0);
}
#[test]
fn opacity_clamps() {
let mut cfg = new_snow_overlay_config();
so_set_opacity(&mut cfg, 5.0);
assert!((cfg.opacity - 1.0).abs() < 1e-6);
}
#[test]
fn average_radius_in_range() {
let cfg = new_snow_overlay_config();
let flakes = generate_snowflakes(5, &cfg);
let avg = so_average_radius(&flakes);
assert!(avg >= cfg.radius_min && avg <= cfg.radius_max + 1e-5);
}
#[test]
fn empty_average_radius_zero() {
assert!(so_average_radius(&[]) < 1e-8);
}
#[test]
fn json_has_keys() {
let j = so_to_json(&new_snow_overlay_config());
assert!(j.contains("flake_count") && j.contains("enabled"));
}
}