use std::collections::HashSet;
use nalgebra_glm::{IVec3, Vec3};
use super::marching_cubes;
use super::resources::SdfWorld;
pub struct SdfCollisionMesh {
pub chunk_size: f32,
pub cell_size: f32,
pub dirty_chunks: HashSet<IVec3>,
pub max_chunks_per_frame: usize,
pub terrain_octaves: u32,
}
impl SdfCollisionMesh {
pub fn new(chunk_size: f32, cell_size: f32) -> Self {
Self {
chunk_size,
cell_size,
dirty_chunks: HashSet::new(),
max_chunks_per_frame: 8,
terrain_octaves: 11,
}
}
pub fn mark_dirty_in_bounds(&mut self, bounds_min: Vec3, bounds_max: Vec3) {
let chunk_min = IVec3::new(
(bounds_min.x / self.chunk_size).floor() as i32,
(bounds_min.y / self.chunk_size).floor() as i32,
(bounds_min.z / self.chunk_size).floor() as i32,
);
let chunk_max = IVec3::new(
(bounds_max.x / self.chunk_size).ceil() as i32,
(bounds_max.y / self.chunk_size).ceil() as i32,
(bounds_max.z / self.chunk_size).ceil() as i32,
);
for cz in chunk_min.z..=chunk_max.z {
for cy in chunk_min.y..=chunk_max.y {
for cx in chunk_min.x..=chunk_max.x {
self.dirty_chunks.insert(IVec3::new(cx, cy, cz));
}
}
}
}
pub fn update(
&mut self,
sdf_world: &SdfWorld,
camera_pos: Vec3,
excluded_edit_indices: &HashSet<usize>,
) -> Vec<(IVec3, CollisionChunkResult)> {
if self.dirty_chunks.is_empty() {
return Vec::new();
}
let count = self.max_chunks_per_frame.min(self.dirty_chunks.len());
let mut candidates: Vec<IVec3> = self.dirty_chunks.iter().copied().collect();
if candidates.len() > count {
candidates.select_nth_unstable_by(count - 1, |a, b| {
let dist_a = chunk_center_distance(*a, self.chunk_size, camera_pos);
let dist_b = chunk_center_distance(*b, self.chunk_size, camera_pos);
dist_a.partial_cmp(&dist_b).unwrap()
});
candidates.truncate(count);
}
let mut results = Vec::with_capacity(count);
let octaves = self.terrain_octaves;
for chunk_coord in candidates {
self.dirty_chunks.remove(&chunk_coord);
let region_min = Vec3::new(
chunk_coord.x as f32 * self.chunk_size,
chunk_coord.y as f32 * self.chunk_size,
chunk_coord.z as f32 * self.chunk_size,
);
let region_max =
region_min + Vec3::new(self.chunk_size, self.chunk_size, self.chunk_size);
let (vertices, indices) = if excluded_edit_indices.is_empty() {
marching_cubes::extract_mesh(
&|point| sdf_world.evaluate_at_lod(point, octaves),
region_min,
region_max,
self.cell_size,
)
} else {
marching_cubes::extract_mesh(
&|point| {
sdf_world.evaluate_at_lod_excluding(point, octaves, excluded_edit_indices)
},
region_min,
region_max,
self.cell_size,
)
};
if vertices.is_empty() || indices.is_empty() {
results.push((chunk_coord, CollisionChunkResult::Empty));
} else {
results.push((
chunk_coord,
CollisionChunkResult::Mesh { vertices, indices },
));
}
}
results
}
pub fn clear(&mut self) {
self.dirty_chunks.clear();
}
}
pub enum CollisionChunkResult {
Empty,
Mesh {
vertices: Vec<[f32; 3]>,
indices: Vec<[u32; 3]>,
},
}
fn chunk_center_distance(chunk_coord: IVec3, chunk_size: f32, camera_pos: Vec3) -> f32 {
let center = Vec3::new(
(chunk_coord.x as f32 + 0.5) * chunk_size,
(chunk_coord.y as f32 + 0.5) * chunk_size,
(chunk_coord.z as f32 + 0.5) * chunk_size,
);
nalgebra_glm::length(&(center - camera_pos))
}