parent
37c83ffb7f
commit
c3077d3ddf
3 changed files with 160 additions and 148 deletions
@ -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, |
||||
} |
@ -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…
Reference in new issue