use rand::Rng;
use rand::SeedableRng;
use rand_pcg::Pcg64Mcg;
use crate::{HeightMap, TerrainGenerator};
#[derive(Debug, Clone)]
pub struct DiamondSquare {
pub seed: u64,
pub roughness: f32,
}
impl DiamondSquare {
pub fn new(seed: u64, roughness: f32) -> Self {
Self { seed, roughness }
}
fn required_size(n: usize) -> usize {
if n <= 2 {
return 2;
}
let mut power = 1usize;
loop {
if power + 1 >= n {
return power + 1;
}
power = power.checked_shl(1).expect(
"HeightMap dimension too large for DiamondSquare (max 2^(usize::BITS-1)+1)",
);
}
}
fn generate_square(&self, size: usize) -> Vec<f32> {
let mut data = vec![0.0_f32; size * size];
let mut rng = Pcg64Mcg::seed_from_u64(self.seed);
macro_rules! get {
($x:expr, $z:expr) => {
data[$z * size + $x]
};
}
macro_rules! set {
($x:expr, $z:expr, $v:expr) => {
data[$z * size + $x] = $v;
};
}
set!(0, 0, rng.random_range(0.0_f32..1.0));
set!(size - 1, 0, rng.random_range(0.0_f32..1.0));
set!(0, size - 1, rng.random_range(0.0_f32..1.0));
set!(size - 1, size - 1, rng.random_range(0.0_f32..1.0));
let mut step = size - 1;
let mut amp = self.roughness;
while step >= 2 {
let half = step / 2;
let mut z = 0;
while z < size - 1 {
let mut x = 0;
while x < size - 1 {
let avg = (get!(x, z)
+ get!(x + step, z)
+ get!(x, z + step)
+ get!(x + step, z + step))
/ 4.0;
let offset = if amp > 0.0 && amp.is_finite() {
rng.random_range(-amp..amp)
} else {
0.0
};
set!(x + half, z + half, avg + offset);
x += step;
}
z += step;
}
let mut z = 0;
while z < size {
let x_start = if (z / half).is_multiple_of(2) {
half
} else {
0
};
let mut x = x_start;
while x < size {
let mut sum = 0.0_f32;
let mut count = 0u32;
if z >= half {
sum += get!(x, z - half);
count += 1;
}
if z + half < size {
sum += get!(x, z + half);
count += 1;
}
if x >= half {
sum += get!(x - half, z);
count += 1;
}
if x + half < size {
sum += get!(x + half, z);
count += 1;
}
let offset = if amp > 0.0 && amp.is_finite() {
rng.random_range(-amp..amp)
} else {
0.0
};
set!(x, z, sum / count as f32 + offset);
x += step;
}
z += half;
}
step = half;
amp *= 0.5_f32.powf(1.0 - self.roughness + 0.5);
}
data
}
}
impl TerrainGenerator for DiamondSquare {
fn generate(&self, heightmap: &mut HeightMap) {
let user_w = heightmap.width();
let user_h = heightmap.height();
let internal_size = Self::required_size(user_w.max(user_h));
let src = self.generate_square(internal_size);
let max_src_x = (internal_size - 1) as f32;
let max_src_z = (internal_size - 1) as f32;
let scale_x = if user_w > 1 {
max_src_x / (user_w - 1) as f32
} else {
0.0
};
let scale_z = if user_h > 1 {
max_src_z / (user_h - 1) as f32
} else {
0.0
};
let dst = heightmap.data_mut();
for z in 0..user_h {
let sz = z as f32 * scale_z;
let z0 = sz.floor() as usize;
let z1 = (z0 + 1).min(internal_size - 1);
let fz = sz - z0 as f32;
for x in 0..user_w {
let sx = x as f32 * scale_x;
let x0 = sx.floor() as usize;
let x1 = (x0 + 1).min(internal_size - 1);
let fx = sx - x0 as f32;
let h00 = src[z0 * internal_size + x0];
let h10 = src[z0 * internal_size + x1];
let h01 = src[z1 * internal_size + x0];
let h11 = src[z1 * internal_size + x1];
let h0 = h00 + (h10 - h00) * fx;
let h1 = h01 + (h11 - h01) * fx;
dst[z * user_w + x] = h0 + (h1 - h0) * fz;
}
}
heightmap.normalize();
}
}