Fix chunk generation

main
copygirl 1 week ago
parent 625348981d
commit aaf97579bc
  1. 119
      src/bloxel/storage/chunked_octree.rs
  2. 34
      src/bloxel/worldgen/mod.rs

@ -71,16 +71,12 @@ impl<T: Default + Copy + Eq> ChunkedOctree<T> {
}
}
pub fn find<'a, W, F>(
&'a self,
from: impl IntoIterator<Item = &'a ChunkPos>,
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<W>,
{
OctreeIterator::new(self, from, priority_fn)
OctreeIterator::new(self, priority_fn)
}
}
@ -102,27 +98,22 @@ where
P: Ord,
F: Fn(OctreeNode, T) -> Option<P>,
{
fn new(
octree: &'a ChunkedOctree<T>,
from: impl IntoIterator<Item = &'a ChunkPos>,
priority_fn: F,
) -> Self {
fn new(octree: &'a ChunkedOctree<T>, 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<usize> {
(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<OctreeNode> 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<T: Default + Copy + Eq, P: Ord> Eq for PriorityItem<T, P> {}
impl<T: Default + Copy + Eq, P: Ord> PartialEq for PriorityItem<T, P> {
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::<bool>::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());

@ -14,18 +14,9 @@ pub fn create_chunks_around_camera(
chunk_map: Single<Entity, With<ChunkMap>>,
) {
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::<Vec<_>>();
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);

Loading…
Cancel
Save