diff --git a/src/bloxel/storage/chunked_octree.rs b/src/bloxel/storage/chunked_octree.rs index 8ecb62f..ceea752 100644 --- a/src/bloxel/storage/chunked_octree.rs +++ b/src/bloxel/storage/chunked_octree.rs @@ -71,16 +71,12 @@ impl ChunkedOctree { } } - pub fn find<'a, W, F>( - &'a self, - from: impl IntoIterator, - priority_fn: F, - ) -> OctreeIterator<'a, T, W, F> + pub fn find<'a, W, F>(&'a self, priority_fn: F) -> OctreeIterator<'a, T, W, F> where W: Ord, F: Fn(OctreeNode, T) -> Option, { - OctreeIterator::new(self, from, priority_fn) + OctreeIterator::new(self, priority_fn) } } @@ -102,27 +98,22 @@ where P: Ord, F: Fn(OctreeNode, T) -> Option

, { - fn new( - octree: &'a ChunkedOctree, - from: impl IntoIterator, - priority_fn: F, - ) -> Self { + fn new(octree: &'a ChunkedOctree, priority_fn: F) -> Self { let mut result = Self { octree, checked: Default::default(), queue: Default::default(), priority_fn, }; - for pos in from { - let node_pos: ZOrderIndex = (*pos).try_into().unwrap(); - result.search_region(node_pos >> octree.depth); + for region_pos in octree.regions.keys() { + result.search_region(*region_pos); } result } fn search_region(&mut self, region_pos: ZOrderIndex) { if self.checked.insert(region_pos) { - self.push_item((self.octree.depth, region_pos).into()); + self.push_item(OctreeNode::new(self.octree.depth, region_pos)); } } @@ -198,30 +189,38 @@ impl OctreeNode { } pub fn children(&self) -> Option<[Self; 8]> { - (self.level > 0).then(|| from_fn(|i| self.child_unchecked(i as u64))) + (self.level > 0).then(|| { + let (level, pos) = (self.level - 1, self.pos << 1); + from_fn(|i| Self::new(level, pos | i as u64)) + }) + } + + pub fn siblings(&self) -> [Self; 8] { + let pos = self.pos & !0b111; + from_fn(|i| Self::new(self.level, pos | i as u64)) } pub fn neighbors(&self) -> [Self; 26] { + #[rustfmt::skip] + const DIRECTIONS: [(i32, i32, i32); 26] = [ + // Sides + (-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1), + // Edges + (-1, -1, 0), (-1, 1, 0), (-1, 0, -1), (-1, 0, 1), + ( 0, -1, -1), ( 0, -1, 1), ( 0, 1, -1), ( 0, 1, 1), + ( 1, -1, 0), ( 1, 1, 0), ( 1, 0, -1), ( 1, 0, 1), + // Corners + (-1, -1, -1), ( 1, -1, -1), (-1, 1, -1), ( 1, 1, -1), + (-1, -1, 1), ( 1, -1, 1), (-1, 1, 1), ( 1, 1, 1), + ]; + let (pos_x, pos_y, pos_z) = self.pos.into(); - let mut result = [OctreeNode::default(); 26]; - let mut index = 0; - for x in -1..1 { - for y in -1..1 { - for z in -1..1 { - if x != 0 && y != 0 && z != 0 { - let pos = zorder(pos_x + x, pos_y + y, pos_z + z); - result[index] = OctreeNode::new(self.level, pos); - index += 1; - } - } - } - } - result + DIRECTIONS.map(|(x, y, z)| Self::new(self.level, zorder(pos_x + x, pos_y + y, pos_z + z))) } pub fn region(&self) -> ChunkRegion { let min = (self.pos << self.level).into(); - let max = min + IVec3::splat((1 << self.level) - 1); + let max = min + IVec3::splat(!(!0 << self.level)); ChunkRegion::new_unchecked(min, max) } @@ -231,19 +230,7 @@ impl OctreeNode { } fn children_index(&self, depth: usize) -> Option { - (self.level > 0).then(|| self.child_unchecked(0).index(depth)) - } - - /// Returns the child of this node with the specified index (within `0..8`). - /// No safety checks are done to ensure that `level` or `index` are valid. - fn child_unchecked(&self, index: u64) -> Self { - Self::new(self.level - 1, (self.pos << 1) | index) - } -} - -impl Into for (usize, ZOrderIndex) { - fn into(self) -> OctreeNode { - OctreeNode::new(self.0, self.1) + (self.level > 0).then(|| Self::new(self.level - 1, self.pos << 1).index(depth)) } } @@ -271,7 +258,7 @@ impl Eq for PriorityItem {} impl PartialEq for PriorityItem { fn eq(&self, other: &Self) -> bool { - self.priority.eq(&other.priority) + (self.priority == other.priority) && (self.node == other.node) } } @@ -293,6 +280,10 @@ mod tests { #[test] fn update() { + let node = |level: usize, pos: (i32, i32, i32)| -> OctreeNode { + OctreeNode::new(level, pos.try_into().unwrap()) + }; + let mut octree = ChunkedOctree::::new(3); assert_eq!(false, octree.get((0, 0, 0))); @@ -302,28 +293,28 @@ mod tests { octree.update((0, 0, 0).into(), |_, _, parent| *parent = true); assert_eq!(true, octree.get((0, 0, 0))); - assert_eq!(true, octree.get((0, zorder(0, 0, 0)))); - assert_eq!(true, octree.get((1, zorder(0, 0, 0)))); - assert_eq!(true, octree.get((2, zorder(0, 0, 0)))); - assert_eq!(true, octree.get((3, zorder(0, 0, 0)))); + assert_eq!(true, octree.get(node(0, (0, 0, 0)))); + assert_eq!(true, octree.get(node(1, (0, 0, 0)))); + assert_eq!(true, octree.get(node(2, (0, 0, 0)))); + assert_eq!(true, octree.get(node(3, (0, 0, 0)))); - assert_eq!(false, octree.get((0, zorder(1, 1, 1)))); - assert_eq!(false, octree.get((1, zorder(2, 2, 2)))); - assert_eq!(false, octree.get((2, zorder(4, 4, 4)))); - assert_eq!(false, octree.get((3, zorder(8, 8, 8)))); + assert_eq!(false, octree.get(node(0, (1, 1, 1)))); + assert_eq!(false, octree.get(node(1, (2, 2, 2)))); + assert_eq!(false, octree.get(node(2, (4, 4, 4)))); + assert_eq!(false, octree.get(node(3, (8, 8, 8)))); - assert_eq!(false, octree.get((0, zorder(-1, -1, -1)))); - assert_eq!(false, octree.get((1, zorder(-1, -1, -1)))); - assert_eq!(false, octree.get((2, zorder(-1, -1, -1)))); - assert_eq!(false, octree.get((3, zorder(-1, -1, -1)))); + assert_eq!(false, octree.get(node(0, (-1, -1, -1)))); + assert_eq!(false, octree.get(node(1, (-1, -1, -1)))); + assert_eq!(false, octree.get(node(2, (-1, -1, -1)))); + assert_eq!(false, octree.get(node(3, (-1, -1, -1)))); octree.update(ChunkPos::new(-12, -17, -42), |_, _, parent| *parent = true); assert_eq!(true, octree.get((-12, -17, -42))); - assert_eq!(true, octree.get((0, zorder(-12, -17, -42)))); - assert_eq!(true, octree.get((1, zorder(-6, -9, -21)))); - assert_eq!(true, octree.get((2, zorder(-3, -5, -11)))); - assert_eq!(true, octree.get((3, zorder(-2, -3, -6)))); + assert_eq!(true, octree.get(node(0, (-12, -17, -42)))); + assert_eq!(true, octree.get(node(1, (-6, -9, -21)))); + assert_eq!(true, octree.get(node(2, (-3, -5, -11)))); + assert_eq!(true, octree.get(node(3, (-2, -3, -6)))); } #[test] @@ -336,9 +327,9 @@ mod tests { octree.update(( 0, 16, -1).into(), |_, _, parent| *parent = true); octree.update(( 9, 9, 9).into(), |_, _, parent| *parent = true); - let from = ChunkPos::new(8, 8, 8); - let mut iterator = octree.find([&from], |node, value| { - value.then(|| -node.region().distance_to_squared(from)) + let pos = ChunkPos::new(8, 8, 8); + let mut iterator = octree.find(|node, value| { + value.then(|| -node.region().distance_to_squared(pos)) }); assert_eq!(Some((( 8, 8, 8).into(), true, -(0*0 + 0*0 + 0*0))), iterator.next()); diff --git a/src/bloxel/worldgen/mod.rs b/src/bloxel/worldgen/mod.rs index f5c7e46..2142c49 100644 --- a/src/bloxel/worldgen/mod.rs +++ b/src/bloxel/worldgen/mod.rs @@ -14,18 +14,9 @@ pub fn create_chunks_around_camera( chunk_map: Single>, ) { let block_pos = camera.translation.as_ivec3().into(); - let (from, _) = ChunkPos::from_block_pos(block_pos); - let mut iterator = octree.find([&from], |node, generated| { - if generated != Generated::All { - let distance = node.region().distance_to_squared(from); - if distance <= 6 * 6 { - return Some(-distance); - } - } - None - }); + let (chunk_pos, _) = ChunkPos::from_block_pos(block_pos); - if let Some((chunk_pos, _, _)) = iterator.next() { + let mut create_chunk = |octree: &mut GeneratedChunks, chunk_pos: ChunkPos| { commands.entity(*chunk_map).with_child((Chunk, chunk_pos)); octree.update(chunk_pos, |_node, children, parent| { let children = children.map(|a| a.as_slice()).unwrap_or_default(); @@ -35,6 +26,25 @@ pub fn create_chunks_around_camera( Generated::Some } }); + }; + + // Create chunk at camera's position, if it's not already. This is necessary because we + // need to "seed" the octree with a region so we have something to begin the search from. + if octree.get(chunk_pos) == Generated::None { + create_chunk(&mut octree, chunk_pos); + } + + let to_generate = octree + .find(|node, generated| { + (generated != Generated::All).then(|| -node.region().distance_to_squared(chunk_pos)) + }) + .take_while(|(_, _, neg_sqr_distance)| *neg_sqr_distance > -(16 * 16)) + .map(|(chunk_pos, _, _)| chunk_pos) + .take(12) // Generate up to 12 chunks per system iteration. + .collect::>(); + + for chunk_pos in to_generate { + create_chunk(&mut octree, chunk_pos); } } @@ -58,7 +68,7 @@ pub fn generate_terrain( for x in 0..size.x { let relative = IVec3::new(x, y, z); let block_pos = chunk_pos.to_block_pos(relative); - let chance = (-block_pos.y as f64 / 32.).clamp(0.001, 1.); + let chance = (-block_pos.y as f64 / 32.).clamp(0., 1.); if rng.random_bool(chance) { let block = *random_blocks.choose(&mut rng).unwrap(); data.set(relative, block);