Compare commits
No commits in common. '5013ded7c957f1aa7e29eeba320577c982f93461' and '37c83ffb7f64cf56623652ca7a91db0c77f20059' have entirely different histories.
5013ded7c9
...
37c83ffb7f
14 changed files with 199 additions and 369 deletions
@ -1,10 +1,24 @@ |
|||||||
use bevy::{platform::collections::HashMap, prelude::*}; |
use bevy::{platform::collections::HashMap, prelude::*}; |
||||||
|
|
||||||
use crate::bloxel::math::ChunkPos; |
use super::{ |
||||||
|
math::{ChunkPos, USize3, CHUNK_LENGTH}, |
||||||
|
storage::PaletteBloxelStorage, |
||||||
|
}; |
||||||
|
|
||||||
#[derive(Component, Default)] |
#[derive(Component, Default)] |
||||||
pub struct Chunk; |
pub struct Chunk; |
||||||
|
|
||||||
|
#[derive(Component, Deref, DerefMut)] |
||||||
|
#[require(Chunk)] |
||||||
|
pub struct ChunkData(PaletteBloxelStorage<Entity>); |
||||||
|
|
||||||
|
impl ChunkData { |
||||||
|
pub fn new(default: Entity) -> Self { |
||||||
|
let len = (CHUNK_LENGTH as u32).try_into().unwrap(); |
||||||
|
Self(PaletteBloxelStorage::new(USize3::splat(len), default)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
#[derive(Component, Default, Deref, DerefMut)] |
#[derive(Component, Default, Deref, DerefMut)] |
||||||
#[require(Transform, Visibility)] |
#[require(Transform, Visibility)] |
||||||
pub struct ChunkMap(HashMap<ChunkPos, Entity>); |
pub struct ChunkMap(HashMap<ChunkPos, Entity>); |
||||||
|
@ -1,90 +0,0 @@ |
|||||||
use bevy::{platform::collections::HashMap, prelude::*}; |
|
||||||
|
|
||||||
use crate::bloxel::prelude::*; |
|
||||||
|
|
||||||
pub struct ChunkedBloxelView<'a, T: BloxelStore<Entity>> { |
|
||||||
region: BlockRegion, |
|
||||||
map: HashMap<ChunkPos, &'a T>, |
|
||||||
} |
|
||||||
|
|
||||||
impl<'a, T: BloxelStore<Entity>> ChunkedBloxelView<'a, T> { |
|
||||||
pub fn new(region: BlockRegion) -> Self { |
|
||||||
let map = HashMap::new(); |
|
||||||
Self { region, map } |
|
||||||
} |
|
||||||
|
|
||||||
pub fn insert(&mut self, chunk_pos: ChunkPos, data: &'a T) { |
|
||||||
self.map.insert(chunk_pos, data); |
|
||||||
} |
|
||||||
|
|
||||||
pub fn get(&self, pos: BlockPos) -> Option<Entity> { |
|
||||||
let (chunk_pos, rel_pos) = ChunkPos::from_block_pos(pos); |
|
||||||
self.map.get(&chunk_pos).and_then(|d| d.get(rel_pos)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<T: BloxelStore<Entity>> BloxelStore<Entity> for ChunkedBloxelView<'_, T> { |
|
||||||
fn size(&self) -> USize3 { |
|
||||||
self.region.size() |
|
||||||
} |
|
||||||
|
|
||||||
fn get(&self, pos: IVec3) -> Option<Entity> { |
|
||||||
ChunkedBloxelView::get(self, self.region.min() + pos) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub struct ChunkedBloxelViewSingleMut<'a, T: BloxelStore<Entity>, M: BloxelStoreMut<Entity>> { |
|
||||||
region: BlockRegion, |
|
||||||
main: (ChunkPos, &'a mut M), |
|
||||||
map: HashMap<ChunkPos, &'a T>, |
|
||||||
} |
|
||||||
|
|
||||||
impl<'a, T: BloxelStore<Entity>, M: BloxelStoreMut<Entity>> ChunkedBloxelViewSingleMut<'a, T, M> { |
|
||||||
pub fn new(region: BlockRegion, main: (ChunkPos, &'a mut M)) -> Self { |
|
||||||
let map = HashMap::new(); |
|
||||||
Self { region, main, map } |
|
||||||
} |
|
||||||
|
|
||||||
pub fn insert(&mut self, chunk_pos: ChunkPos, data: &'a T) { |
|
||||||
assert!(chunk_pos != self.main.0); |
|
||||||
self.map.insert(chunk_pos, data); |
|
||||||
} |
|
||||||
|
|
||||||
pub fn get(&self, pos: BlockPos) -> Option<Entity> { |
|
||||||
let (chunk_pos, rel_pos) = ChunkPos::from_block_pos(pos); |
|
||||||
if chunk_pos == self.main.0 { |
|
||||||
self.main.1.get(rel_pos) |
|
||||||
} else { |
|
||||||
self.map.get(&chunk_pos).and_then(|d| d.get(rel_pos)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub fn set(&mut self, pos: BlockPos, value: Entity) -> Result<Entity, ()> { |
|
||||||
let (chunk_pos, rel_pos) = ChunkPos::from_block_pos(pos); |
|
||||||
if chunk_pos == self.main.0 { |
|
||||||
self.main.1.set(rel_pos, value) |
|
||||||
} else { |
|
||||||
Err(()) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<'a, T: BloxelStore<Entity>, M: BloxelStoreMut<Entity>> BloxelStore<Entity> |
|
||||||
for ChunkedBloxelViewSingleMut<'a, T, M> |
|
||||||
{ |
|
||||||
fn size(&self) -> USize3 { |
|
||||||
self.region.size() |
|
||||||
} |
|
||||||
|
|
||||||
fn get(&self, pos: IVec3) -> Option<Entity> { |
|
||||||
ChunkedBloxelViewSingleMut::get(self, self.region.min() + pos) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<'a, T: BloxelStore<Entity>, M: BloxelStoreMut<Entity>> BloxelStoreMut<Entity> |
|
||||||
for ChunkedBloxelViewSingleMut<'a, T, M> |
|
||||||
{ |
|
||||||
fn set(&mut self, pos: IVec3, value: Entity) -> Result<Entity, ()> { |
|
||||||
ChunkedBloxelViewSingleMut::set(self, self.region.min() + pos, value) |
|
||||||
} |
|
||||||
} |
|
@ -1,12 +1,10 @@ |
|||||||
mod bloxel_array; |
mod bloxel_array; |
||||||
mod bloxel_store; |
mod bloxel_store; |
||||||
mod chunked_bloxel_view; |
|
||||||
mod chunked_octree; |
mod chunked_octree; |
||||||
mod palette_bloxel_storage; |
mod palette_bloxel_storage; |
||||||
mod palette_storage; |
mod palette_storage; |
||||||
|
|
||||||
pub use bloxel_array::*; |
pub use bloxel_array::*; |
||||||
pub use bloxel_store::*; |
pub use bloxel_store::*; |
||||||
pub use chunked_bloxel_view::*; |
|
||||||
pub use chunked_octree::*; |
pub use chunked_octree::*; |
||||||
pub use palette_bloxel_storage::*; |
pub use palette_bloxel_storage::*; |
||||||
|
@ -1,112 +0,0 @@ |
|||||||
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,45 +0,0 @@ |
|||||||
use bevy::prelude::*; |
|
||||||
use noise_functions::{Noise, Simplex}; |
|
||||||
|
|
||||||
use crate::{ |
|
||||||
bloxel::{prelude::*, storage::PaletteBloxelStorage}, |
|
||||||
TerrainBlocks, |
|
||||||
}; |
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut)] |
|
||||||
#[require(Chunk)] |
|
||||||
pub struct TerrainShape(PaletteBloxelStorage<Entity>); |
|
||||||
|
|
||||||
pub fn generate_terrain_shape( |
|
||||||
mut commands: Commands, |
|
||||||
terrain_blocks: Option<Res<TerrainBlocks>>, |
|
||||||
chunks_without_data: Query<(Entity, &ChunkPos), (With<Chunk>, Without<TerrainShape>)>, |
|
||||||
) { |
|
||||||
let Some(terrain) = terrain_blocks else { |
|
||||||
return; |
|
||||||
}; |
|
||||||
|
|
||||||
for (entity, chunk_pos) in chunks_without_data.iter() { |
|
||||||
let mut data = TerrainShape(PaletteBloxelStorage::new(CHUNK_SIZE, 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 { |
|
||||||
terrain.rock |
|
||||||
} else { |
|
||||||
terrain.air |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
commands.entity(entity).insert(data); |
|
||||||
} |
|
||||||
} |
|
@ -1,70 +0,0 @@ |
|||||||
use std::ops::{Deref, DerefMut}; |
|
||||||
|
|
||||||
use bevy::prelude::*; |
|
||||||
use cart_prod::specs::Hom2FCartProd; |
|
||||||
|
|
||||||
use crate::{ |
|
||||||
bloxel::{ |
|
||||||
prelude::*, |
|
||||||
storage::{ChunkedBloxelViewSingleMut, PaletteBloxelStorage}, |
|
||||||
worldgen::TerrainShape, |
|
||||||
}, |
|
||||||
TerrainBlocks, |
|
||||||
}; |
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut)] |
|
||||||
#[require(Chunk)] |
|
||||||
pub struct TopLayer(PaletteBloxelStorage<Entity>); |
|
||||||
|
|
||||||
pub fn generate_top_layer( |
|
||||||
mut commands: Commands, |
|
||||||
terrain_blocks: Option<Res<TerrainBlocks>>, |
|
||||||
|
|
||||||
mut chunks_without_top_layer: Query< |
|
||||||
(Entity, &ChunkPos, &TerrainShape), |
|
||||||
(With<Chunk>, Without<TopLayer>), |
|
||||||
>, |
|
||||||
|
|
||||||
chunk_map: Single<&ChunkMap>, |
|
||||||
chunks_with_shape: Query<&TerrainShape>, |
|
||||||
) { |
|
||||||
let Some(terrain) = terrain_blocks else { |
|
||||||
return; |
|
||||||
}; |
|
||||||
|
|
||||||
for (entity, chunk_pos, shape) in chunks_without_top_layer.iter_mut() { |
|
||||||
let above_pos = chunk_pos + Neighbor::Top; |
|
||||||
let Some(above_entity) = chunk_map.get(&above_pos).copied() else { |
|
||||||
continue; |
|
||||||
}; |
|
||||||
let Ok(above_shape) = chunks_with_shape.get(above_entity) else { |
|
||||||
continue; |
|
||||||
}; |
|
||||||
|
|
||||||
let region = BlockRegion::new_unchecked( |
|
||||||
chunk_pos.to_block_pos(IVec3::ZERO), |
|
||||||
above_pos.to_block_pos(CHUNK_MAX), |
|
||||||
); |
|
||||||
let mut data = TopLayer((*shape.deref()).clone()); |
|
||||||
let mut chunks = ChunkedBloxelViewSingleMut::new(region, (*chunk_pos, data.deref_mut())); |
|
||||||
chunks.insert(above_pos, above_shape.deref()); |
|
||||||
|
|
||||||
for [x, z] in Hom2FCartProd::new(0..CHUNK_LENGTH as i32, 0..CHUNK_LENGTH as i32) { |
|
||||||
let mut air = CHUNK_LENGTH; // Some large value.
|
|
||||||
for y in (0..20).rev() { |
|
||||||
let pos = chunk_pos.to_block_pos(IVec3::new(x, y, z)); |
|
||||||
let existing = chunks.get(pos).unwrap(); |
|
||||||
if existing == terrain.air { |
|
||||||
air = 0; |
|
||||||
} else if y < CHUNK_LENGTH as i32 && air <= 4 { |
|
||||||
let new = (air == 1).then_some(terrain.grass).unwrap_or(terrain.dirt); |
|
||||||
chunks.set(pos, new).unwrap(); |
|
||||||
} |
|
||||||
air += 1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
drop(chunks); // Unsure why this is required.
|
|
||||||
commands.entity(entity).insert(data); |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue