Split worldgen systems into multiple files

main
copygirl 5 days ago
parent 37c83ffb7f
commit c3077d3ddf
  1. 112
      src/bloxel/worldgen/chunk_loading.rs
  2. 156
      src/bloxel/worldgen/mod.rs
  3. 40
      src/bloxel/worldgen/terrain_shape.rs

@ -0,0 +1,112 @@
use bevy::prelude::*;
use crate::{
bloxel::{
prelude::*,
storage::{ChunkedOctree, OctreeNode},
},
camera_controller::ControlledCamera,
};
const LOAD_DISTANCE: usize = 12;
const UNLOAD_DISTANCE: usize = 16;
const CHUNKS_PER_ITERATION: usize = 12;
pub fn create_chunks_around_camera(
mut commands: Commands,
mut octree: ResMut<ExistingChunks>,
camera: Single<&Transform, With<ControlledCamera>>,
chunk_map: Single<Entity, With<ChunkMap>>,
) {
let block_pos = camera.translation.as_ivec3().into();
let (chunk_pos, _) = ChunkPos::from_block_pos(block_pos);
let mut create_chunk = |octree: &mut ExistingChunks, pos: ChunkPos| {
commands.entity(*chunk_map).with_child((Chunk, pos));
octree.update(pos, |_, children, parent| {
let children = children.map(|a| a.as_slice()).unwrap_or_default();
*parent = if children.iter().all(|c| *c == Existing::All) {
Existing::All
} else {
Existing::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) == Existing::None {
create_chunk(&mut octree, chunk_pos);
}
let sqr_load_distance = (LOAD_DISTANCE * LOAD_DISTANCE) as i32;
let to_create = octree
.find(|node, exist| {
(exist != Existing::All).then(|| -node.region().distance_to_squared(chunk_pos))
})
.take_while(|(_, _, neg_sqr_distance)| *neg_sqr_distance > -sqr_load_distance)
.map(|(chunk_pos, _, _)| chunk_pos)
.take(CHUNKS_PER_ITERATION) // Create up to this many chunks per system iteration.
.collect::<Vec<_>>();
for chunk_pos in to_create {
create_chunk(&mut octree, chunk_pos);
}
}
pub fn destroy_chunks_away_from_camera(
mut commands: Commands,
mut octree: ResMut<ExistingChunks>,
camera: Single<&Transform, With<ControlledCamera>>,
chunk_map: Single<&ChunkMap>,
) {
let block_pos = camera.translation.as_ivec3().into();
let (chunk_pos, _) = ChunkPos::from_block_pos(block_pos);
let distance = |node: OctreeNode| -> i32 {
let (min, max) = node.region().into();
let a = (chunk_pos - min).length_squared();
let b = (chunk_pos - max).length_squared();
a.max(b)
};
let sqr_unload_distance = (UNLOAD_DISTANCE * UNLOAD_DISTANCE) as i32;
let to_destroy = octree
.find(|node, exist| (exist != Existing::None).then(|| distance(node)))
.take_while(|(_, _, sqr_distance)| *sqr_distance > sqr_unload_distance)
.map(|(chunk_pos, _, _)| chunk_pos)
.collect::<Vec<_>>();
for chunk_pos in to_destroy {
let chunk = *chunk_map.get(&chunk_pos).unwrap();
commands.entity(chunk).despawn();
octree.update(chunk_pos, |_, children, parent| {
let children = children.map(|a| a.as_slice()).unwrap_or_default();
*parent = if children.iter().all(|c| *c == Existing::None) {
Existing::None
} else {
Existing::Some
}
});
}
}
#[derive(Resource, Deref, DerefMut)]
pub struct ExistingChunks {
octree: ChunkedOctree<Existing>,
}
impl Default for ExistingChunks {
fn default() -> Self {
let octree = ChunkedOctree::new(5);
Self { octree }
}
}
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Existing {
#[default]
None,
Some,
All,
}

@ -1,18 +1,10 @@
use bevy::prelude::*; use bevy::prelude::*;
use noise_functions::{Noise, Simplex};
use crate::{ mod chunk_loading;
bloxel::{ mod terrain_shape;
prelude::*,
storage::{ChunkedOctree, OctreeNode},
},
camera_controller::ControlledCamera,
TerrainBlocks,
};
const LOAD_DISTANCE: usize = 12; use chunk_loading::*;
const UNLOAD_DISTANCE: usize = 16; use terrain_shape::*;
const CHUNKS_PER_ITERATION: usize = 12;
pub struct WorldGenPlugin; pub struct WorldGenPlugin;
@ -21,143 +13,11 @@ impl Plugin for WorldGenPlugin {
app.init_resource::<ExistingChunks>().add_systems( app.init_resource::<ExistingChunks>().add_systems(
Update, Update,
( (
create_chunks_around_camera,
destroy_chunks_away_from_camera, destroy_chunks_away_from_camera,
generate_terrain, create_chunks_around_camera,
), generate_terrain_shape,
)
.chain(),
); );
} }
} }
fn create_chunks_around_camera(
mut commands: Commands,
mut octree: ResMut<ExistingChunks>,
camera: Single<&Transform, With<ControlledCamera>>,
chunk_map: Single<Entity, With<ChunkMap>>,
) {
let block_pos = camera.translation.as_ivec3().into();
let (chunk_pos, _) = ChunkPos::from_block_pos(block_pos);
let mut create_chunk = |octree: &mut ExistingChunks, pos: ChunkPos| {
commands.entity(*chunk_map).with_child((Chunk, pos));
octree.update(pos, |_, children, parent| {
let children = children.map(|a| a.as_slice()).unwrap_or_default();
*parent = if children.iter().all(|c| *c == Existing::All) {
Existing::All
} else {
Existing::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) == Existing::None {
create_chunk(&mut octree, chunk_pos);
}
let sqr_load_distance = (LOAD_DISTANCE * LOAD_DISTANCE) as i32;
let to_create = octree
.find(|node, exist| {
(exist != Existing::All).then(|| -node.region().distance_to_squared(chunk_pos))
})
.take_while(|(_, _, neg_sqr_distance)| *neg_sqr_distance > -sqr_load_distance)
.map(|(chunk_pos, _, _)| chunk_pos)
.take(CHUNKS_PER_ITERATION) // Create up to this many chunks per system iteration.
.collect::<Vec<_>>();
for chunk_pos in to_create {
create_chunk(&mut octree, chunk_pos);
}
}
fn destroy_chunks_away_from_camera(
mut commands: Commands,
mut octree: ResMut<ExistingChunks>,
camera: Single<&Transform, With<ControlledCamera>>,
chunk_map: Single<&ChunkMap>,
) {
let block_pos = camera.translation.as_ivec3().into();
let (chunk_pos, _) = ChunkPos::from_block_pos(block_pos);
let distance = |node: OctreeNode| -> i32 {
let (min, max) = node.region().into();
let a = (chunk_pos - min).length_squared();
let b = (chunk_pos - max).length_squared();
a.max(b)
};
let sqr_unload_distance = (UNLOAD_DISTANCE * UNLOAD_DISTANCE) as i32;
let to_destroy = octree
.find(|node, exist| (exist != Existing::None).then(|| distance(node)))
.take_while(|(_, _, sqr_distance)| *sqr_distance > sqr_unload_distance)
.map(|(chunk_pos, _, _)| chunk_pos)
.collect::<Vec<_>>();
for chunk_pos in to_destroy {
let chunk = *chunk_map.get(&chunk_pos).unwrap();
commands.entity(chunk).despawn();
octree.update(chunk_pos, |_, children, parent| {
let children = children.map(|a| a.as_slice()).unwrap_or_default();
*parent = if children.iter().all(|c| *c == Existing::None) {
Existing::None
} else {
Existing::Some
}
});
}
}
fn generate_terrain(
mut commands: Commands,
terrain_blocks: Option<Res<TerrainBlocks>>,
chunks_without_data: Query<(Entity, &ChunkPos), (With<Chunk>, Without<ChunkData>)>,
) {
let Some(terrain) = terrain_blocks else {
return;
};
let slices = [terrain.rock, terrain.dirt, terrain.grass, terrain.sand];
for (entity, chunk_pos) in chunks_without_data.iter() {
let mut data = ChunkData::new(terrain.air);
data.update(|relative, _| {
let block_pos = chunk_pos.to_block_pos(relative);
let float_pos = IVec3::from(block_pos).as_vec3();
let bias = ((float_pos.y + 32.) / 64.).clamp(-0.25, 1.);
let sample = Simplex
.fbm(3, 0.65, 2.0)
.weighted(0.4)
.frequency(0.01)
.sample3(float_pos);
if sample > bias {
slices[relative.y as usize % slices.len()]
} else {
terrain.air
}
});
commands.entity(entity).insert(data);
}
}
#[derive(Resource, Deref, DerefMut)]
pub struct ExistingChunks {
octree: ChunkedOctree<Existing>,
}
impl Default for ExistingChunks {
fn default() -> Self {
let octree = ChunkedOctree::new(5);
Self { octree }
}
}
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Existing {
#[default]
None,
Some,
All,
}

@ -0,0 +1,40 @@
use bevy::prelude::*;
use noise_functions::{Noise, Simplex};
use crate::{bloxel::prelude::*, TerrainBlocks};
pub fn generate_terrain_shape(
mut commands: Commands,
terrain_blocks: Option<Res<TerrainBlocks>>,
chunks_without_data: Query<(Entity, &ChunkPos), (With<Chunk>, Without<ChunkData>)>,
) {
let Some(terrain) = terrain_blocks else {
return;
};
let slices = [terrain.rock, terrain.dirt, terrain.grass, terrain.sand];
for (entity, chunk_pos) in chunks_without_data.iter() {
let mut data = ChunkData::new(terrain.air);
data.update(|relative, _| {
let block_pos = chunk_pos.to_block_pos(relative);
let float_pos = IVec3::from(block_pos).as_vec3();
let bias = ((float_pos.y + 32.) / 64.).clamp(-0.25, 1.);
let sample = Simplex
.fbm(3, 0.65, 2.0)
.weighted(0.4)
.frequency(0.01)
.sample3(float_pos);
if sample > bias {
slices[relative.y as usize % slices.len()]
} else {
terrain.air
}
});
commands.entity(entity).insert(data);
}
}
Loading…
Cancel
Save