use glam::Vec3;
use super::Heightmap;
use crate::mesh::{Mesh, Vertex};
pub struct ClipmapTerrain {
pub levels: Vec<ClipmapLevel>,
pub camera_pos: Vec3,
pub grid_resolution: u32,
}
#[derive(Debug, Clone)]
pub struct ClipmapLevel {
pub level: u32,
pub spacing: f32,
pub origin_x: f32,
pub origin_z: f32,
pub resolution: u32,
}
pub struct ClipmapMesh {
pub level: u32,
pub mesh: Mesh,
}
impl ClipmapTerrain {
pub fn new(num_levels: u32, grid_resolution: u32, base_spacing: f32) -> Self {
let mut levels = Vec::with_capacity(num_levels as usize);
for i in 0..num_levels {
let spacing = base_spacing * (1 << i) as f32;
levels.push(ClipmapLevel {
level: i,
spacing,
origin_x: 0.0,
origin_z: 0.0,
resolution: grid_resolution,
});
}
Self {
levels,
camera_pos: Vec3::ZERO,
grid_resolution,
}
}
pub fn update(&mut self, camera_pos: Vec3) {
self.camera_pos = camera_pos;
for level in &mut self.levels {
let half_extent = level.spacing * level.resolution as f32 * 0.5;
level.origin_x = ((camera_pos.x - half_extent) / level.spacing).floor() * level.spacing;
level.origin_z = ((camera_pos.z - half_extent) / level.spacing).floor() * level.spacing;
}
}
pub fn generate_meshes(&self, heightmap: &Heightmap) -> Vec<ClipmapMesh> {
self.levels
.iter()
.map(|level| {
let mesh = generate_level_mesh(level, heightmap);
ClipmapMesh {
level: level.level,
mesh,
}
})
.collect()
}
pub fn generate_level_mesh(
&self,
level_idx: u32,
heightmap: &Heightmap,
) -> Option<ClipmapMesh> {
self.levels.get(level_idx as usize).map(|level| {
let mesh = generate_level_mesh(level, heightmap);
ClipmapMesh {
level: level.level,
mesh,
}
})
}
pub fn level_count(&self) -> usize {
self.levels.len()
}
pub fn total_vertices(&self) -> usize {
self.levels.len() * (self.grid_resolution * self.grid_resolution) as usize
}
}
fn generate_level_mesh(level: &ClipmapLevel, heightmap: &Heightmap) -> Mesh {
let res = level.resolution;
let spacing = level.spacing;
let mut vertices = Vec::with_capacity((res * res) as usize);
let mut indices = Vec::with_capacity(((res - 1) * (res - 1) * 6) as usize);
for gz in 0..res {
for gx in 0..res {
let wx = (gx as f32) * spacing + level.origin_x;
let wz = (gz as f32) * spacing + level.origin_z;
let wy = heightmap.sample(wx, wz);
let normal = heightmap.normal_at(wx, wz);
let uv_x = gx as f32 / (res - 1) as f32;
let uv_z = gz as f32 / (res - 1) as f32;
vertices.push(Vertex {
position: Vec3::new(wx, wy, wz),
normal,
uv: glam::Vec2::new(uv_x, uv_z),
..Default::default()
});
}
}
for gz in 0..(res - 1) {
for gx in 0..(res - 1) {
let i00 = gz * res + gx;
let i10 = gz * res + gx + 1;
let i01 = (gz + 1) * res + gx;
let i11 = (gz + 1) * res + gx + 1;
indices.push(i00);
indices.push(i01);
indices.push(i10);
indices.push(i10);
indices.push(i01);
indices.push(i11);
}
}
Mesh { vertices, indices }
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_heightmap() -> Heightmap {
let mut hm = Heightmap::new(32, 32, 100.0, 100.0);
hm.generate_fbm(3, 0.5, 2.0, 42);
hm.normalize();
hm.scale_heights(10.0);
hm
}
#[test]
fn test_clipmap_new() {
let clipmap = ClipmapTerrain::new(4, 32, 1.0);
assert_eq!(clipmap.level_count(), 4);
assert_eq!(clipmap.levels[0].spacing, 1.0);
assert_eq!(clipmap.levels[1].spacing, 2.0);
assert_eq!(clipmap.levels[2].spacing, 4.0);
assert_eq!(clipmap.levels[3].spacing, 8.0);
}
#[test]
fn test_clipmap_update() {
let mut clipmap = ClipmapTerrain::new(3, 16, 1.0);
clipmap.update(Vec3::new(50.0, 0.0, 50.0));
for level in &clipmap.levels {
let remainder_x = level.origin_x % level.spacing;
let remainder_z = level.origin_z % level.spacing;
assert!(
remainder_x.abs() < 0.001,
"Origin X not snapped at level {}",
level.level
);
assert!(
remainder_z.abs() < 0.001,
"Origin Z not snapped at level {}",
level.level
);
}
}
#[test]
fn test_generate_meshes() {
let hm = make_test_heightmap();
let mut clipmap = ClipmapTerrain::new(3, 8, 1.0);
clipmap.update(Vec3::new(50.0, 0.0, 50.0));
let meshes = clipmap.generate_meshes(&hm);
assert_eq!(meshes.len(), 3);
for cm in &meshes {
assert_eq!(cm.mesh.vertices.len(), 64); assert_eq!(cm.mesh.indices.len(), 7 * 7 * 6); }
}
#[test]
fn test_level_mesh_has_normals() {
let hm = make_test_heightmap();
let mut clipmap = ClipmapTerrain::new(1, 8, 2.0);
clipmap.update(Vec3::new(50.0, 0.0, 50.0));
let meshes = clipmap.generate_meshes(&hm);
for v in &meshes[0].mesh.vertices {
let len = v.normal.length();
assert!(
(len - 1.0).abs() < 1e-6,
"Normal must be unit length, got {}",
len
);
}
}
#[test]
fn test_total_vertices() {
let clipmap = ClipmapTerrain::new(4, 32, 1.0);
assert_eq!(clipmap.total_vertices(), 4 * 32 * 32);
}
#[test]
fn test_generate_single_level() {
let hm = make_test_heightmap();
let mut clipmap = ClipmapTerrain::new(3, 8, 1.0);
clipmap.update(Vec3::new(50.0, 0.0, 50.0));
let mesh = clipmap.generate_level_mesh(1, &hm);
assert!(mesh.is_some());
assert_eq!(mesh.unwrap().mesh.vertices.len(), 64);
let invalid = clipmap.generate_level_mesh(99, &hm);
assert!(invalid.is_none());
}
}