use std::sync::OnceLock;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct HeightMap {
data: Vec<f32>,
width: usize,
height: usize,
scale: f32,
#[serde(default)]
lakes: Vec<Lake>,
#[serde(skip)]
normals: OnceLock<Vec<[f32; 3]>>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Lake {
pub index: usize,
pub depth: f32,
pub area: f32,
}
impl Clone for HeightMap {
fn clone(&self) -> Self {
Self {
data: self.data.clone(),
width: self.width,
height: self.height,
scale: self.scale,
lakes: self.lakes.clone(),
normals: OnceLock::new(),
}
}
}
impl HeightMap {
pub fn new(width: usize, height: usize, scale: f32) -> Self {
assert!(width > 0 && height > 0, "dimensions must be positive");
assert!(scale > 0.0, "scale must be positive");
Self {
data: vec![0.0; width * height],
width,
height,
scale,
lakes: Vec::new(),
normals: OnceLock::new(),
}
}
#[inline]
pub fn width(&self) -> usize {
self.width
}
#[inline]
pub fn height(&self) -> usize {
self.height
}
#[inline]
pub fn scale(&self) -> f32 {
self.scale
}
#[inline]
pub fn data(&self) -> &[f32] {
&self.data
}
#[inline]
pub fn data_mut(&mut self) -> &mut [f32] {
self.invalidate_caches();
&mut self.data
}
#[inline]
pub fn get(&self, x: usize, z: usize) -> f32 {
self.data[z * self.width + x]
}
#[inline]
pub fn get_mut(&mut self, x: usize, z: usize) -> &mut f32 {
self.invalidate_caches();
&mut self.data[z * self.width + x]
}
#[inline]
pub fn set(&mut self, x: usize, z: usize, val: f32) {
self.invalidate_caches();
self.data[z * self.width + x] = val;
}
#[inline]
pub fn get_clamped(&self, x: i32, z: i32) -> f32 {
let cx = x.clamp(0, self.width as i32 - 1) as usize;
let cz = z.clamp(0, self.height as i32 - 1) as usize;
self.get(cx, cz)
}
pub fn get_height_at(&self, world_x: f32, world_z: f32) -> f32 {
let gx = world_x / self.scale;
let gz = world_z / self.scale;
let x0 = gx.floor() as i32;
let z0 = gz.floor() as i32;
let fx = gx - x0 as f32;
let fz = gz - z0 as f32;
let h00 = self.get_clamped(x0, z0);
let h10 = self.get_clamped(x0 + 1, z0);
let h01 = self.get_clamped(x0, z0 + 1);
let h11 = self.get_clamped(x0 + 1, z0 + 1);
let h0 = h00 + (h10 - h00) * fx;
let h1 = h01 + (h11 - h01) * fx;
h0 + (h1 - h0) * fz
}
pub fn get_normal_at(&self, world_x: f32, world_z: f32) -> [f32; 3] {
let step = self.scale;
let hl = self.get_height_at(world_x - step, world_z);
let hr = self.get_height_at(world_x + step, world_z);
let hd = self.get_height_at(world_x, world_z - step);
let hu = self.get_height_at(world_x, world_z + step);
let dhdx = (hr - hl) / (2.0 * step);
let dhdz = (hu - hd) / (2.0 * step);
let nx = -dhdx;
let ny = 1.0_f32;
let nz = -dhdz;
let len = (nx * nx + ny * ny + nz * nz).sqrt();
if !len.is_finite() {
return [0.0, 1.0, 0.0];
}
[nx / len, ny / len, nz / len]
}
pub fn normal_at_grid(&self, x: usize, z: usize) -> [f32; 3] {
self.normals_grid()[z * self.width + x]
}
pub fn normals_grid(&self) -> &[[f32; 3]] {
self.normals.get_or_init(|| self.compute_normals())
}
fn compute_normals(&self) -> Vec<[f32; 3]> {
let w = self.width;
let h = self.height;
let mut out = Vec::with_capacity(w * h);
let step = self.scale;
let inv_2step = 1.0_f32 / (2.0 * step);
for z in 0..h {
for x in 0..w {
let hl = self.get_clamped(x as i32 - 1, z as i32);
let hr = self.get_clamped(x as i32 + 1, z as i32);
let hd = self.get_clamped(x as i32, z as i32 - 1);
let hu = self.get_clamped(x as i32, z as i32 + 1);
let dhdx = (hr - hl) * inv_2step;
let dhdz = (hu - hd) * inv_2step;
let nx = -dhdx;
let ny = 1.0_f32;
let nz = -dhdz;
let len = (nx * nx + ny * ny + nz * nz).sqrt();
if !len.is_finite() {
out.push([0.0, 1.0, 0.0]);
} else {
out.push([nx / len, ny / len, nz / len]);
}
}
}
out
}
pub fn lakes(&self) -> &[Lake] {
&self.lakes
}
pub(crate) fn set_lakes(&mut self, lakes: Vec<Lake>) {
self.lakes = lakes;
}
fn invalidate_caches(&mut self) {
self.normals.take();
}
pub fn normalize(&mut self) {
let min = self.data.iter().cloned().fold(f32::INFINITY, f32::min);
let max = self.data.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let range = max - min;
if range > f32::EPSILON {
for v in &mut self.data {
*v = (*v - min) / range;
}
}
self.invalidate_caches();
}
pub fn world_width(&self) -> f32 {
self.width as f32 * self.scale
}
pub fn world_depth(&self) -> f32 {
self.height as f32 * self.scale
}
}