use crate::HeightMap;
#[derive(Debug, Clone)]
pub struct ThermalErosion {
pub iterations: u32,
pub talus_angle: f32,
pub fraction: f32,
pub water_level: f32,
pub underwater_talus_angle: f32,
}
impl ThermalErosion {
pub fn new() -> Self {
Self {
iterations: 50,
talus_angle: 0.05,
fraction: 0.25,
water_level: 0.0,
underwater_talus_angle: 0.1,
}
}
pub fn with_iterations(mut self, n: u32) -> Self {
self.iterations = n;
self
}
pub fn with_talus_angle(mut self, angle: f32) -> Self {
self.talus_angle = angle;
self
}
pub fn with_underwater_talus_angle(mut self, angle: f32) -> Self {
self.underwater_talus_angle = angle;
self
}
pub fn with_water_level(mut self, level: f32) -> Self {
self.water_level = level;
self
}
pub fn erode(&self, heightmap: &mut HeightMap) {
let fraction = self.fraction.clamp(f32::EPSILON, 0.25);
let w = heightmap.width();
let h = heightmap.height();
const NEIGHBOURS: [(i32, i32); 4] = [(1, 0), (-1, 0), (0, 1), (0, -1)];
let mut delta = vec![0.0_f32; w * h];
for _ in 0..self.iterations {
delta.fill(0.0);
for z in 0..h {
for x in 0..w {
let h_here = heightmap.get(x, z);
let mut total_excess = 0.0_f32;
let mut excess = [0.0_f32; 4];
for (i, &(dx, dz)) in NEIGHBOURS.iter().enumerate() {
let nx = x as i32 + dx;
let nz = z as i32 + dz;
if nx < 0 || nx >= w as i32 || nz < 0 || nz >= h as i32 {
continue;
}
let h_nb = heightmap.get(nx as usize, nz as usize);
let diff = h_here - h_nb;
let current_talus =
if h_here <= self.water_level && h_nb <= self.water_level {
self.underwater_talus_angle
} else {
self.talus_angle
};
if diff > current_talus {
excess[i] = diff - current_talus;
total_excess += excess[i];
}
}
if total_excess <= 0.0 {
continue;
}
for (i, &(dx, dz)) in NEIGHBOURS.iter().enumerate() {
if excess[i] <= 0.0 {
continue;
}
let nx = x as i32 + dx;
let nz = z as i32 + dz;
if nx < 0 || nx >= w as i32 || nz < 0 || nz >= h as i32 {
continue;
}
let transfer = fraction * excess[i];
delta[z * w + x] -= transfer;
delta[nz as usize * w + nx as usize] += transfer;
}
}
}
for (v, d) in heightmap.data_mut().iter_mut().zip(delta.iter()) {
*v += d;
}
}
}
}
impl Default for ThermalErosion {
fn default() -> Self {
Self::new()
}
}