use std::sync::Arc;
use bevy::math::{UVec2, Vec2};
pub(crate) const LEAF_CELLS: u32 = 32;
pub(crate) const LACUNARITY: f32 = 2.0;
pub(crate) const GAIN: f32 = 0.5;
pub(crate) const OCTAVE_ROTATION: f32 = 2.399_96;
pub(crate) const F_HALF: f32 = 0.33;
pub(crate) const R_LOW: f32 = 0.21;
pub(crate) const R_SPAN: f32 = 0.71;
pub(crate) const AABB_PAD_M: f32 = 2.0;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TerrainGrid {
pub origin: Vec2,
pub cell_m: f32,
pub size: UVec2,
pub y0: f32,
}
impl TerrainGrid {
pub fn extent(&self) -> Vec2 {
self.size.as_vec2() * self.cell_m
}
pub fn contains(&self, p: Vec2) -> bool {
let local = p - self.origin;
local.cmpge(Vec2::ZERO).all() && local.cmple(self.extent()).all()
}
pub fn index(&self, c: bevy::math::IVec2) -> usize {
let m = self.size.as_ivec2() - 1;
let k = c.clamp(bevy::math::IVec2::ZERO, m).as_uvec2();
(k.y * self.size.x + k.x) as usize
}
pub fn sample(&self, layer: &[f32], p: Vec2) -> f32 {
let g = (p - self.origin) / self.cell_m - 0.5;
let base = g.floor();
let w = g - base;
let b = base.as_ivec2();
let at = |dx: i32, dz: i32| layer[self.index(b + bevy::math::IVec2::new(dx, dz))];
let row0 = at(0, 0) * (1.0 - w.x) + at(1, 0) * w.x;
let row1 = at(0, 1) * (1.0 - w.x) + at(1, 1) * w.x;
row0 * (1.0 - w.y) + row1 * w.y
}
}
pub(crate) fn resolve_resolution(extent: Vec2, resolution: UVec2, max: u32) -> (UVec2, f32, bool) {
let snap = |r: u32| {
let r = r.clamp(LEAF_CELLS, max.max(LEAF_CELLS));
1 << r.ilog2()
};
let res = UVec2::new(snap(resolution.x), snap(resolution.y));
let cell = extent.x / res.x as f32;
let adjusted = ((extent.y / res.y as f32) - cell).abs() > cell * 1e-4;
if !adjusted {
return (res, cell, false);
}
let rows = ((extent.y / cell / LEAF_CELLS as f32).round().max(1.0) as u32) * LEAF_CELLS;
(UVec2::new(res.x, rows), cell, true)
}
pub(crate) fn octave_count(requested: u32, feature_m: f32, cell_m: f32) -> u32 {
let nyquist = (feature_m / (2.0 * cell_m)).max(1.0).log2().floor() as u32 + 1;
requested.min(nyquist).max(1)
}
pub(crate) fn seed_mix(seed: u64) -> u32 {
(seed.wrapping_mul(2_654_435_761) % 4096) as u32
}
#[derive(Clone, Debug)]
pub struct TerrainSnapshot {
pub grid: TerrainGrid,
pub surface: Arc<[f32]>,
pub version: u32,
}
impl TerrainSnapshot {
pub fn height(&self, p: Vec2) -> Option<f32> {
self.grid.contains(p).then(|| self.height_clamped(p))
}
pub fn height_clamped(&self, p: Vec2) -> f32 {
self.grid.y0 + self.grid.sample(&self.surface, p)
}
}
#[cfg(test)]
pub(crate) mod mirror;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_resolution_is_clamped_and_rounded_to_a_power_of_two() {
let e = Vec2::splat(2048.0);
assert_eq!(
resolve_resolution(e, UVec2::splat(1024), 1024),
(UVec2::splat(1024), 2.0, false)
);
assert_eq!(
resolve_resolution(e, UVec2::splat(4096), 2048).0,
UVec2::splat(2048)
);
assert_eq!(
resolve_resolution(e, UVec2::splat(1024), 256).0,
UVec2::splat(256)
);
assert_eq!(
resolve_resolution(e, UVec2::splat(1000), 2048).0,
UVec2::splat(512)
);
assert_eq!(
resolve_resolution(e, UVec2::splat(1), 2048).0,
UVec2::splat(32)
);
}
#[test]
fn square_cells_are_kept_by_rederiving_the_rows() {
let (res, cell, adjusted) =
resolve_resolution(Vec2::new(2048.0, 1024.0), UVec2::splat(1024), 1024);
assert_eq!(res, UVec2::new(1024, 512));
assert_eq!(cell, 2.0);
assert!(adjusted);
let (res, _, adjusted) =
resolve_resolution(Vec2::new(1000.0, 1500.0), UVec2::splat(256), 256);
assert!(adjusted);
assert_eq!(res.y % LEAF_CELLS, 0);
assert_eq!(res.y, 384);
}
#[test]
fn octaves_stop_at_the_nyquist_limit() {
assert_eq!(octave_count(8, 600.0, 2.0), 8);
assert_eq!(octave_count(8, 300.0, 2.0), 7);
assert_eq!(octave_count(8, 1500.0, 2.0), 8);
assert_eq!(octave_count(8, 800.0, 2.0), 8);
assert_eq!(octave_count(8, 600.0, 8.0), 6);
assert_eq!(octave_count(0, 600.0, 2.0), 1);
}
#[test]
fn sampling_is_bilinear_with_cell_centres_at_half() {
let grid = TerrainGrid {
origin: Vec2::new(-10.0, -10.0),
cell_m: 2.0,
size: UVec2::new(4, 4),
y0: 5.0,
};
let layer: Vec<f32> = (0..16)
.map(|i| (i % 4) as f32 + (i / 4) as f32 * 10.0)
.collect();
assert!((grid.sample(&layer, Vec2::new(-7.0, -5.0)) - 21.0).abs() < 1e-5);
assert!((grid.sample(&layer, Vec2::new(-6.0, -5.0)) - 21.5).abs() < 1e-5);
let snap = TerrainSnapshot {
grid,
surface: layer.into(),
version: 1,
};
assert_eq!(snap.height(Vec2::new(-7.0, -5.0)), Some(26.0));
assert_eq!(snap.height(Vec2::new(-10.1, 0.0)), None);
assert!(snap.height(Vec2::new(-2.0, -2.0)).is_some());
assert_eq!(snap.height(Vec2::new(-1.9, -2.0)), None);
}
}