use crate::mesh3d::{Mesh3D, Vec3, Vertex};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Terrain {
pub mesh: Mesh3D,
pub width: u32,
pub depth: u32,
pub height_scale: f32,
pub heightmap: Vec<Vec<f32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TerrainConfig {
pub width: u32,
pub depth: u32,
pub height_scale: f32,
pub octaves: u32,
pub persistence: f32,
pub lacunarity: f32,
pub seed: u32,
}
impl Terrain {
pub fn new(config: TerrainConfig) -> Self {
let heightmap = Self::generate_heightmap(&config);
let mesh = Self::create_mesh_from_heightmap(&heightmap, &config);
Self {
mesh,
width: config.width,
depth: config.depth,
height_scale: config.height_scale,
heightmap,
}
}
pub fn flat(width: u32, depth: u32) -> Self {
let config = TerrainConfig {
width,
depth,
height_scale: 0.0,
octaves: 1,
persistence: 0.5,
lacunarity: 2.0,
seed: 0,
};
Self::new(config)
}
pub fn hills(width: u32, depth: u32, height: f32) -> Self {
let config = TerrainConfig {
width,
depth,
height_scale: height,
octaves: 4,
persistence: 0.5,
lacunarity: 2.0,
seed: 42,
};
Self::new(config)
}
pub fn mountains(width: u32, depth: u32, height: f32) -> Self {
let config = TerrainConfig {
width,
depth,
height_scale: height,
octaves: 6,
persistence: 0.6,
lacunarity: 2.5,
seed: 123,
};
Self::new(config)
}
fn generate_heightmap(config: &TerrainConfig) -> Vec<Vec<f32>> {
let mut heightmap = vec![vec![0.0; config.depth as usize]; config.width as usize];
for x in 0..config.width {
for z in 0..config.depth {
let mut height = 0.0;
let mut amplitude = 1.0;
let mut frequency = 1.0;
for _ in 0..config.octaves {
let sample_x = x as f32 * frequency / config.width as f32;
let sample_z = z as f32 * frequency / config.depth as f32;
let noise = Self::perlin_noise(sample_x, sample_z, config.seed);
height += noise * amplitude;
amplitude *= config.persistence;
frequency *= config.lacunarity;
}
heightmap[x as usize][z as usize] = height * config.height_scale;
}
}
heightmap
}
fn perlin_noise(x: f32, z: f32, seed: u32) -> f32 {
let xi = x.floor() as i32;
let zi = z.floor() as i32;
let xf = x - xi as f32;
let zf = z - zi as f32;
let n00 = Self::hash(xi, zi, seed);
let n10 = Self::hash(xi + 1, zi, seed);
let n01 = Self::hash(xi, zi + 1, seed);
let n11 = Self::hash(xi + 1, zi + 1, seed);
let nx0 = Self::lerp(n00, n10, xf);
let nx1 = Self::lerp(n01, n11, xf);
Self::lerp(nx0, nx1, zf)
}
fn hash(x: i32, z: i32, seed: u32) -> f32 {
let mut n = (x.wrapping_mul(374761393) + z.wrapping_mul(668265263)) as u32;
n = n.wrapping_add(seed);
n = (n ^ (n >> 13)).wrapping_mul(1274126177);
n = n ^ (n >> 16);
(n as f32 / u32::MAX as f32) * 2.0 - 1.0
}
fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + (b - a) * t
}
fn create_mesh_from_heightmap(heightmap: &[Vec<f32>], config: &TerrainConfig) -> Mesh3D {
let mut vertices = Vec::new();
let mut indices = Vec::new();
let width = config.width as usize;
let depth = config.depth as usize;
for x in 0..width {
for z in 0..depth {
let height = heightmap[x][z];
let pos = Vec3::new(
x as f32 - width as f32 / 2.0,
height,
z as f32 - depth as f32 / 2.0,
);
let normal = Self::calculate_normal(heightmap, x, z, width, depth);
let color = Self::height_to_color(height, config.height_scale);
vertices.push(Vertex {
position: pos,
normal,
uv: (x as f32 / width as f32, z as f32 / depth as f32),
color,
});
}
}
for x in 0..(width - 1) {
for z in 0..(depth - 1) {
let i0 = (x * depth + z) as u32;
let i1 = ((x + 1) * depth + z) as u32;
let i2 = ((x + 1) * depth + z + 1) as u32;
let i3 = (x * depth + z + 1) as u32;
indices.extend_from_slice(&[i0, i1, i2, i2, i3, i0]);
}
}
Mesh3D::finalize(vertices, indices)
}
fn calculate_normal(heightmap: &[Vec<f32>], x: usize, z: usize, width: usize, depth: usize) -> Vec3 {
let h = heightmap[x][z];
let hx = if x < width - 1 { heightmap[x + 1][z] } else { h };
let hz = if z < depth - 1 { heightmap[x][z + 1] } else { h };
let dx = Vec3::new(1.0, hx - h, 0.0);
let dz = Vec3::new(0.0, hz - h, 1.0);
dx.cross(&dz).normalize()
}
fn height_to_color(height: f32, max_height: f32) -> [u8; 4] {
let normalized = (height / max_height).max(0.0).min(1.0);
if normalized < 0.3 {
[50, 100, 200, 255]
} else if normalized < 0.5 {
[200, 180, 100, 255]
} else if normalized < 0.7 {
[50, 150, 50, 255]
} else if normalized < 0.9 {
[100, 100, 100, 255]
} else {
[255, 255, 255, 255]
}
}
pub fn get_height_at(&self, x: f32, z: f32) -> f32 {
let grid_x = (x + self.width as f32 / 2.0) as usize;
let grid_z = (z + self.depth as f32 / 2.0) as usize;
if grid_x < self.width as usize && grid_z < self.depth as usize {
self.heightmap[grid_x][grid_z]
} else {
0.0
}
}
pub fn smooth(&mut self, iterations: u32) {
for _ in 0..iterations {
let mut new_heightmap = self.heightmap.clone();
for x in 1..(self.width as usize - 1) {
for z in 1..(self.depth as usize - 1) {
let sum = self.heightmap[x - 1][z]
+ self.heightmap[x + 1][z]
+ self.heightmap[x][z - 1]
+ self.heightmap[x][z + 1]
+ self.heightmap[x][z];
new_heightmap[x][z] = sum / 5.0;
}
}
self.heightmap = new_heightmap;
}
let config = TerrainConfig {
width: self.width,
depth: self.depth,
height_scale: self.height_scale,
octaves: 1,
persistence: 0.5,
lacunarity: 2.0,
seed: 0,
};
self.mesh = Self::create_mesh_from_heightmap(&self.heightmap, &config);
}
}
impl Default for TerrainConfig {
fn default() -> Self {
Self {
width: 50,
depth: 50,
height_scale: 10.0,
octaves: 4,
persistence: 0.5,
lacunarity: 2.0,
seed: 42,
}
}
}