use super::Animation;
use crate::generators::{ColorGradient, ColorStop, EmitterConfig, ParticleSystem};
use crate::render::Canvas;
use rand::RngExt;
struct WaterDrop {
x: f64,
y: f64,
vy: f64,
brightness: f64,
}
pub struct Waterfall {
width: usize,
height: usize,
drops: Vec<WaterDrop>,
mist: ParticleSystem,
fall_x: f64,
fall_width: f64,
rng: rand::rngs::ThreadRng,
}
impl Waterfall {
pub fn new(width: usize, height: usize, scale: f64) -> Self {
let fall_x = width as f64 * 0.5;
let fall_width = (width as f64 * 0.25).max(8.0);
let count = ((width * height) as f64 / 40.0 * scale) as usize;
let mut rng = rand::rng();
let drops = (0..count)
.map(|_| WaterDrop {
x: fall_x + rng.random_range(-fall_width * 0.5..fall_width * 0.5),
y: rng.random_range(0.0..height as f64),
vy: rng.random_range(15.0..35.0),
brightness: rng.random_range(0.4..1.0),
})
.collect();
let mist_config = EmitterConfig {
x: fall_x,
y: height as f64 * 0.9,
spread: std::f64::consts::PI * 1.0,
angle: -std::f64::consts::FRAC_PI_2,
speed_min: 5.0,
speed_max: 25.0,
life_min: 0.8,
life_max: 3.0,
gravity: -2.0,
drag: 0.95,
wind: 0.0,
gradient: ColorGradient::new(vec![
ColorStop {
t: 0.0,
r: 200,
g: 220,
b: 255,
},
ColorStop {
t: 0.5,
r: 150,
g: 180,
b: 220,
},
ColorStop {
t: 1.0,
r: 100,
g: 130,
b: 180,
},
]),
};
Waterfall {
width,
height,
drops,
mist: ParticleSystem::new(mist_config, (1500.0 * scale) as usize),
fall_x,
fall_width,
rng: rand::rng(),
}
}
}
impl Animation for Waterfall {
fn name(&self) -> &str {
"waterfall"
}
fn on_resize(&mut self, width: usize, height: usize) {
self.width = width;
self.height = height;
}
fn update(&mut self, canvas: &mut Canvas, dt: f64, time: f64) {
let h = self.height as f64;
self.fall_x = self.width as f64 * 0.5;
self.fall_width = (self.width as f64 * 0.25).max(8.0);
let pool_y = h * 0.85;
canvas.clear();
let rock_left = (self.fall_x - self.fall_width * 0.7) as usize;
let rock_right = (self.fall_x + self.fall_width * 0.7) as usize;
for x in rock_left..rock_right.min(canvas.width) {
for y in 0..(pool_y as usize).min(canvas.height) {
let noise = ((x as f64 * 0.3 + y as f64 * 0.2).sin() * 0.1 + 0.1).max(0.0);
let shade = 0.15 + noise;
let r = (60.0 * shade / 0.25) as u8;
let g = (65.0 * shade / 0.25) as u8;
let b = (70.0 * shade / 0.25) as u8;
canvas.set_colored(x, y, 0.3, r, g, b);
}
}
for drop in &mut self.drops {
drop.vy += 20.0 * dt; drop.y += drop.vy * dt;
drop.x += (time * 2.0 + drop.y * 0.1).sin() * 0.3;
if drop.y >= pool_y {
drop.y = self.rng.random_range(-5.0..0.0);
drop.x = self.fall_x
+ self
.rng
.random_range(-self.fall_width * 0.5..self.fall_width * 0.5);
drop.vy = self.rng.random_range(15.0..35.0);
drop.brightness = self.rng.random_range(0.4..1.0);
}
let px = drop.x as usize;
let py = drop.y as usize;
if px < canvas.width && py < canvas.height {
let speed_factor = (drop.vy / 35.0).clamp(0.3, 1.0);
let b_val = (180.0 + 75.0 * speed_factor) as u8;
let g_val = (200.0 + 40.0 * speed_factor) as u8;
canvas.set_colored(px, py, drop.brightness, 180, g_val, b_val);
if drop.vy > 20.0 {
let streak_len = (drop.vy * 0.05) as usize;
for s in 1..streak_len.min(4) {
let sy = py.wrapping_sub(s);
if sy < canvas.height {
let fade = 1.0 - s as f64 / streak_len as f64;
canvas.set_colored(
px,
sy,
drop.brightness * fade * 0.5,
160,
190,
b_val,
);
}
}
}
}
}
for y in (pool_y as usize)..canvas.height {
for x in 0..canvas.width {
let depth = (y as f64 - pool_y) / (h - pool_y);
let wave = ((x as f64 * 0.2 + time * 3.0).sin() * 0.05).max(0.0);
let brightness = 0.4 - depth * 0.2 + wave;
let r = (40.0 - depth * 20.0) as u8;
let g = (80.0 + depth * 30.0) as u8;
let b = (160.0 + depth * 40.0).min(220.0) as u8;
canvas.set_colored(x, y, brightness, r, g, b);
}
}
self.mist.config.x = self.fall_x
+ self
.rng
.random_range(-self.fall_width * 0.5..self.fall_width * 0.5);
self.mist.config.y = pool_y;
self.mist.config.wind = (time * 0.3).sin() * 5.0;
self.mist.emit(self.rng.random_range(4..10));
self.mist.update(dt);
self.mist.draw(canvas);
}
}