use rand::Rng;
use rand::SeedableRng;
use rand_pcg::Pcg64Mcg;
use crate::{HeightMap, TerrainGenerator};
#[derive(Debug, Clone)]
pub struct VoronoiTerracing {
pub seed: u64,
pub num_seeds: usize,
pub num_terraces: usize,
}
impl VoronoiTerracing {
pub fn new(seed: u64, num_seeds: usize, num_terraces: usize) -> Self {
assert!(num_seeds > 0, "num_seeds must be > 0");
assert!(num_terraces > 0, "num_terraces must be > 0");
Self {
seed,
num_seeds,
num_terraces,
}
}
}
impl TerrainGenerator for VoronoiTerracing {
fn generate(&self, heightmap: &mut HeightMap) {
let mut rng = Pcg64Mcg::seed_from_u64(self.seed);
let seeds: Vec<(f32, f32)> = (0..self.num_seeds)
.map(|_| (rng.random::<f32>(), rng.random::<f32>()))
.collect();
let seed_heights: Vec<f32> = (0..self.num_seeds)
.map(|i| i as f32 / (self.num_seeds - 1).max(1) as f32)
.collect();
let grid_size = ((self.num_seeds as f64).sqrt().ceil() as usize).max(1);
let cell_w = 1.0_f32 / grid_size as f32;
let mut grid: Vec<Vec<usize>> = vec![vec![]; grid_size * grid_size];
for (i, &(sx, sz)) in seeds.iter().enumerate() {
let gx = ((sx / cell_w) as usize).min(grid_size - 1);
let gz = ((sz / cell_w) as usize).min(grid_size - 1);
grid[gz * grid_size + gx].push(i);
}
let w = heightmap.width();
let h = heightmap.height();
for z in 0..h {
for x in 0..w {
let nx = x as f32 / w as f32;
let nz = z as f32 / h as f32;
let cx = ((nx / cell_w) as usize).min(grid_size - 1);
let cz = ((nz / cell_w) as usize).min(grid_size - 1);
let mut best_dist = f32::MAX;
let mut nearest = 0usize;
for r in 0..=(grid_size as i32) {
let min_ring_dist_sq = if r == 0 {
0.0f32
} else {
let d = (r - 1) as f32 * cell_w;
d * d
};
if min_ring_dist_sq > best_dist {
break;
}
for dz in -r..=r {
for dx in -r..=r {
if dx.abs() < r && dz.abs() < r {
continue;
}
let gx = cx as i32 + dx;
let gz = cz as i32 + dz;
if gx < 0 || gz < 0 || gx >= grid_size as i32 || gz >= grid_size as i32
{
continue;
}
for &si in &grid[gz as usize * grid_size + gx as usize] {
let (sx, sz) = seeds[si];
let d = (sx - nx).powi(2) + (sz - nz).powi(2);
if d < best_dist {
best_dist = d;
nearest = si;
}
}
}
}
}
let raw = seed_heights[nearest];
let terraced = (raw * self.num_terraces as f32)
.floor()
.min(self.num_terraces as f32 - 1.0)
/ self.num_terraces as f32;
heightmap.set(x, z, terraced);
}
}
}
}