use std::collections::HashMap;
use crate::{HeightMap, TerrainGenerator};
#[derive(Debug, Clone)]
pub struct HeightMapTile {
pub coord: (i32, i32),
pub heightmap: HeightMap,
}
pub struct TiledHeightMap {
tile_size: usize,
scale: f32,
base_seed: u64,
generator_factory: Box<dyn Fn(u64) -> Box<dyn TerrainGenerator>>,
tiles: HashMap<(i32, i32), HeightMapTile>,
}
impl std::fmt::Debug for TiledHeightMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TiledHeightMap")
.field("tile_size", &self.tile_size)
.field("scale", &self.scale)
.field("base_seed", &self.base_seed)
.field("loaded_tiles", &self.tiles.len())
.finish()
}
}
impl TiledHeightMap {
pub fn new<F>(tile_size: usize, scale: f32, base_seed: u64, generator_factory: F) -> Self
where
F: Fn(u64) -> Box<dyn TerrainGenerator> + 'static,
{
assert!(tile_size > 0, "tile_size must be > 0");
assert!(scale > 0.0, "scale must be positive");
Self {
tile_size,
scale,
base_seed,
generator_factory: Box::new(generator_factory),
tiles: HashMap::new(),
}
}
#[inline]
pub fn tile_size(&self) -> usize {
self.tile_size
}
#[inline]
pub fn tile_world_size(&self) -> f32 {
self.tile_size as f32 * self.scale
}
#[inline]
pub fn scale(&self) -> f32 {
self.scale
}
#[inline]
pub fn loaded_count(&self) -> usize {
self.tiles.len()
}
pub fn tile_for_world(&self, world_x: f32, world_z: f32) -> (i32, i32) {
let tw = self.tile_world_size();
let tx = (world_x / tw).floor() as i32;
let tz = (world_z / tw).floor() as i32;
(tx, tz)
}
pub fn tile(&mut self, coord: (i32, i32)) -> &HeightMapTile {
if !self.tiles.contains_key(&coord) {
self.generate_into_cache(coord);
}
self.tiles
.get(&coord)
.expect("tile inserted by generate_into_cache")
}
pub fn ensure_tile(&mut self, coord: (i32, i32)) {
if !self.tiles.contains_key(&coord) {
self.generate_into_cache(coord);
}
}
pub fn ensure_radius(&mut self, centre: (i32, i32), radius: i32) {
for dz in -radius..=radius {
for dx in -radius..=radius {
self.ensure_tile((centre.0 + dx, centre.1 + dz));
}
}
}
pub fn evict_outside(&mut self, centre: (i32, i32), radius: i32) -> usize {
let before = self.tiles.len();
self.tiles.retain(|&(tx, tz), _| {
let dx = (tx - centre.0).abs();
let dz = (tz - centre.1).abs();
dx <= radius && dz <= radius
});
before - self.tiles.len()
}
pub fn loaded_tile(&self, coord: (i32, i32)) -> Option<&HeightMapTile> {
self.tiles.get(&coord)
}
pub fn loaded_tiles(&self) -> impl Iterator<Item = &HeightMapTile> {
self.tiles.values()
}
pub fn sample_height_at(&mut self, world_x: f32, world_z: f32) -> f32 {
let coord = self.tile_for_world(world_x, world_z);
let tile = self.tile(coord);
let tw = tile.heightmap.world_width();
let local_x = world_x - coord.0 as f32 * tw;
let local_z = world_z - coord.1 as f32 * tw;
tile.heightmap.get_height_at(local_x, local_z)
}
fn generate_into_cache(&mut self, coord: (i32, i32)) {
let seed = derive_tile_seed(self.base_seed, coord.0, coord.1);
let mut hm = HeightMap::new(self.tile_size, self.tile_size, self.scale);
let generator = (self.generator_factory)(seed);
generator.generate(&mut hm);
self.tiles.insert(
coord,
HeightMapTile {
coord,
heightmap: hm,
},
);
}
}
pub fn derive_tile_seed(base_seed: u64, tile_x: i32, tile_z: i32) -> u64 {
let packed = ((tile_x as u32 as u64) << 32) | (tile_z as u32 as u64);
splitmix64(base_seed.wrapping_add(splitmix64(packed)))
}
fn splitmix64(mut x: u64) -> u64 {
x = x.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = x;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}