use nalgebra_glm::Vec3;
use std::collections::HashMap;
use std::sync::Arc;
pub const TERRAIN_CACHE_SIZE: u32 = 512;
pub const TERRAIN_TILE_TEXELS: u32 = 64;
pub const TERRAIN_GRASS_LEVEL: u32 = 1;
pub const TERRAIN_COLLIDER_LAYER: u32 = 10;
pub const TERRAIN_PICK_ID: u32 = 0x7F7F_FFF0;
pub const TERRAIN_COLLIDER_TILE_METERS: f32 = 64.0;
pub const TERRAIN_COLLIDER_SAMPLES: usize = 65;
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct TerrainSettings {
pub enabled: bool,
pub seed: u32,
pub texel_size: f32,
pub level_count: u32,
pub continental_spline: [[f32; 2]; 8],
pub erosion_spline: [[f32; 2]; 8],
pub continental_frequency: f32,
pub erosion_frequency: f32,
pub ridge_frequency: f32,
pub warp_frequency: f32,
pub warp_strength: f32,
pub detail_amplitude: f32,
pub erosion_sharpness: f32,
pub height_min: f32,
pub height_max: f32,
pub rock_slope: f32,
pub snow_height: f32,
pub rock_color: [f32; 4],
pub snow_color: [f32; 4],
pub origin_flatten_radius: f32,
pub collider_radius: i32,
pub revision: u64,
}
impl Default for TerrainSettings {
fn default() -> Self {
Self {
enabled: false,
seed: 7,
texel_size: 0.5,
level_count: 10,
continental_spline: [
[-1.0, -30.0],
[-0.4, -8.0],
[-0.1, 0.0],
[0.25, 2.0],
[0.5, 14.0],
[0.7, 40.0],
[0.85, 90.0],
[1.0, 140.0],
],
erosion_spline: [
[-1.0, 0.02],
[-0.5, 0.08],
[-0.1, 0.2],
[0.2, 0.45],
[0.45, 0.7],
[0.65, 1.0],
[0.85, 1.0],
[1.0, 1.0],
],
continental_frequency: 1.0 / 4000.0,
erosion_frequency: 1.0 / 1400.0,
ridge_frequency: 1.0 / 320.0,
warp_frequency: 1.0 / 600.0,
warp_strength: 120.0,
detail_amplitude: 1.4,
erosion_sharpness: 1.25,
height_min: -60.0,
height_max: 320.0,
rock_slope: 0.55,
snow_height: 120.0,
rock_color: [0.16, 0.13, 0.11, 1.0],
snow_color: [0.75, 0.78, 0.85, 1.0],
origin_flatten_radius: 0.0,
collider_radius: 2,
revision: 1,
}
}
}
pub enum TerrainSnapshotState {
Requested,
Encoded,
Mapping,
}
pub struct TerrainSnapshot {
pub buffer: wgpu::Buffer,
pub map_done: Arc<std::sync::atomic::AtomicBool>,
pub origin_texels: [i32; 2],
pub texel_size: f32,
pub revision: u64,
pub state: TerrainSnapshotState,
}
#[derive(Default)]
pub struct TerrainShared {
pub cache_ready: bool,
pub cache_view: Option<wgpu::TextureView>,
pub grass_origin_texels: [i32; 2],
pub grass_texel_size: f32,
pub snapshot_request: Option<[f32; 2]>,
pub snapshot: Option<TerrainSnapshot>,
}
#[derive(Clone)]
pub struct TerrainShare {
#[cfg(not(target_arch = "wasm32"))]
inner: Arc<std::sync::Mutex<TerrainShared>>,
#[cfg(target_arch = "wasm32")]
inner: std::rc::Rc<std::cell::RefCell<TerrainShared>>,
}
impl Default for TerrainShare {
fn default() -> Self {
Self {
#[cfg(not(target_arch = "wasm32"))]
inner: Arc::new(std::sync::Mutex::new(TerrainShared::default())),
#[cfg(target_arch = "wasm32")]
inner: std::rc::Rc::new(std::cell::RefCell::new(TerrainShared::default())),
}
}
}
impl TerrainShare {
pub fn with<Output>(&self, action: impl FnOnce(&mut TerrainShared) -> Output) -> Output {
#[cfg(not(target_arch = "wasm32"))]
{
action(&mut self.inner.lock().expect("terrain share poisoned"))
}
#[cfg(target_arch = "wasm32")]
{
action(&mut self.inner.borrow_mut())
}
}
}
pub struct TerrainColliderTile {
pub entity: crate::entity::RenderEntity,
}
#[derive(Default)]
pub struct TerrainRenderState {
pub share: TerrainShare,
pub collider_tiles: HashMap<(i32, i32), TerrainColliderTile>,
pub collider_revision: u64,
pub height_tiles: HashMap<(i32, i32), Vec<f32>>,
}
impl TerrainRenderState {
pub fn sample_height(&self, x: f32, z: f32) -> Option<f32> {
let tile_x = (x / TERRAIN_COLLIDER_TILE_METERS).floor() as i32;
let tile_z = (z / TERRAIN_COLLIDER_TILE_METERS).floor() as i32;
let heights = self.height_tiles.get(&(tile_x, tile_z))?;
let local_x = x - tile_x as f32 * TERRAIN_COLLIDER_TILE_METERS;
let local_z = z - tile_z as f32 * TERRAIN_COLLIDER_TILE_METERS;
let sample_x = local_x.clamp(0.0, TERRAIN_COLLIDER_TILE_METERS - 0.001);
let sample_z = local_z.clamp(0.0, TERRAIN_COLLIDER_TILE_METERS - 0.001);
let column = sample_x.floor() as usize;
let row = sample_z.floor() as usize;
let fraction_x = sample_x - column as f32;
let fraction_z = sample_z - row as f32;
let stride = TERRAIN_COLLIDER_SAMPLES;
let h00 = heights[row * stride + column];
let h10 = heights[row * stride + column + 1];
let h01 = heights[(row + 1) * stride + column];
let h11 = heights[(row + 1) * stride + column + 1];
let x0 = h00 + (h10 - h00) * fraction_x;
let x1 = h01 + (h11 - h01) * fraction_x;
Some(x0 + (x1 - x0) * fraction_z)
}
pub fn collider_ready(&self, center: Vec3, radius: i32) -> bool {
let center_tile_x = (center.x / TERRAIN_COLLIDER_TILE_METERS).floor() as i32;
let center_tile_z = (center.z / TERRAIN_COLLIDER_TILE_METERS).floor() as i32;
for offset_z in -radius..=radius {
for offset_x in -radius..=radius {
let coord = (center_tile_x + offset_x, center_tile_z + offset_z);
if !self.collider_tiles.contains_key(&coord) {
return false;
}
}
}
true
}
}