use crate::{
active_clipmap_lod_chunks, Array3x1, ChunkKey3, ChunkMap3, ChunkUnits3, ChunkedOctreeSet,
ClipMapConfig3, ClipMapUpdate3, GetMut, IterChunkKeys, LodChunkUpdate3, OctreeSet,
SmallKeyHashMap,
};
use building_blocks_core::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Deserialize, Serialize)]
pub struct OctreeChunkIndex {
pub superchunk_octrees: ChunkedOctreeSet,
chunk_shape: Point3i,
}
impl OctreeChunkIndex {
#[inline]
pub fn chunk_shape(&self) -> Point3i {
self.chunk_shape
}
#[inline]
pub fn superchunk_shape(&self) -> Point3i {
self.superchunk_octrees.indexer.chunk_shape()
}
pub fn index_chunk_map<T, Ch, Store>(
superchunk_shape: Point3i,
chunk_map: &ChunkMap3<T, Ch, Store>,
) -> Self
where
Store: for<'r> IterChunkKeys<'r, [i32; 3]>,
{
let chunk_shape = chunk_map.indexer.chunk_shape();
Self::index_lod0_chunks(
superchunk_shape,
chunk_shape,
chunk_map.storage().chunk_keys().filter(|k| k.lod == 0),
)
}
pub fn index_lod0_chunks<'a>(
superchunk_shape: Point3i,
chunk_shape: Point3i,
chunk_keys: impl Iterator<Item = &'a ChunkKey3>,
) -> Self {
assert!(superchunk_shape.dimensions_are_powers_of_2());
assert!(chunk_shape.dimensions_are_powers_of_2());
assert!(chunk_shape.is_cube());
let superchunk_log2 = superchunk_shape.map_components_unary(|c| c.trailing_zeros() as i32);
let chunk_log2 = chunk_shape.map_components_unary(|c| c.trailing_zeros() as i32);
assert!(superchunk_log2 > chunk_log2);
assert!(
superchunk_log2 - chunk_log2 < Point3i::fill(6),
"OctreeSet only support 6 levels. Make your chunk shape larger or make your superchunk shape smaller.
superchunk shape = {:?}, log2 = {:?}
chunk shape = {:?}, log2 = {:?}",
superchunk_shape,
superchunk_log2,
chunk_shape,
chunk_log2
);
let superchunk_shape_in_chunks = superchunk_shape >> chunk_log2;
let superchunk_mask = !(superchunk_shape - Point3i::ONES);
let mut superchunk_bitsets = SmallKeyHashMap::default();
for chunk_key in chunk_keys {
assert_eq!(chunk_key.lod, 0);
let superchunk_key = chunk_key.minimum & superchunk_mask;
let bitset = superchunk_bitsets.entry(superchunk_key).or_insert_with(|| {
Array3x1::fill(
Extent3i::from_min_and_shape(
superchunk_key >> chunk_log2,
superchunk_shape_in_chunks,
),
false,
)
});
*bitset.get_mut(chunk_key.minimum >> chunk_log2) = true;
}
let mut superchunk_octrees = SmallKeyHashMap::default();
for (lod_chunk_key, bitset) in superchunk_bitsets.into_iter() {
let octree = OctreeSet::from_array3(&bitset, *bitset.extent());
superchunk_octrees.insert(lod_chunk_key, octree);
}
Self {
superchunk_octrees: ChunkedOctreeSet::new(superchunk_shape, superchunk_octrees),
chunk_shape,
}
}
pub fn clipmap_config(&self, clip_box_radius: u16) -> ClipMapConfig3 {
assert!(self.superchunk_octrees.indexer.chunk_shape().is_cube());
assert!(self.chunk_shape().is_cube());
let superchunk_log2 = self
.superchunk_octrees
.indexer
.chunk_shape()
.x()
.trailing_zeros() as u8;
let chunk_log2 = self.chunk_shape().x().trailing_zeros() as u8;
let num_lods = superchunk_log2 - chunk_log2 + 1;
ClipMapConfig3::new(num_lods, clip_box_radius, self.chunk_shape())
}
pub fn active_clipmap_lod_chunks(
&self,
extent: &Extent3i,
clip_box_radius: u16,
lod0_center: ChunkUnits3,
mut init_rx: impl FnMut(ChunkKey3),
) {
let config = self.clipmap_config(clip_box_radius);
self.superchunk_octrees
.visit_octrees(extent, &mut |octree| {
active_clipmap_lod_chunks(&config, octree, lod0_center, &mut init_rx)
});
}
pub fn find_clipmap_chunk_updates(
&self,
extent: &Extent3i,
clip_box_radius: u16,
old_lod0_center: ChunkUnits3,
new_lod0_center: ChunkUnits3,
mut update_rx: impl FnMut(LodChunkUpdate3),
) {
let update = ClipMapUpdate3::new(
&self.clipmap_config(clip_box_radius),
old_lod0_center,
new_lod0_center,
);
self.superchunk_octrees
.visit_octrees(extent, &mut |octree| {
update.find_chunk_updates(octree, &mut update_rx)
});
}
}