Compare commits

..

5 Commits

  1. 16
      src/bloxel/chunk.rs
  2. 9
      src/bloxel/math/chunk_pos.rs
  3. 8
      src/bloxel/math/chunk_region.rs
  4. 4
      src/bloxel/math/size.rs
  5. 44
      src/bloxel/mesh/mod.rs
  6. 1
      src/bloxel/storage/bloxel_array.rs
  7. 90
      src/bloxel/storage/chunked_bloxel_view.rs
  8. 2
      src/bloxel/storage/mod.rs
  9. 1
      src/bloxel/storage/palette_bloxel_storage.rs
  10. 2
      src/bloxel/storage/palette_storage.rs
  11. 112
      src/bloxel/worldgen/chunk_loading.rs
  12. 164
      src/bloxel/worldgen/mod.rs
  13. 45
      src/bloxel/worldgen/terrain_shape.rs
  14. 70
      src/bloxel/worldgen/top_layer.rs

@ -1,24 +1,10 @@
use bevy::{platform::collections::HashMap, prelude::*}; use bevy::{platform::collections::HashMap, prelude::*};
use super::{ use crate::bloxel::math::ChunkPos;
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,4 +1,7 @@
use std::ops::{self, Index, IndexMut}; use std::{
num::NonZeroU32,
ops::{self, Index, IndexMut},
};
use bevy::{ use bevy::{
ecs::component::Component, ecs::component::Component,
@ -8,11 +11,15 @@ use bevy::{
use overload::overload; use overload::overload;
use super::{BlockPos, BlockRegion, ChunkRegion}; use super::{BlockPos, BlockRegion, ChunkRegion};
use crate::bloxel::math::USize3;
pub const CHUNK_SHIFT: i32 = 4; pub const CHUNK_SHIFT: i32 = 4;
pub const CHUNK_MASK: i32 = !(!0 << CHUNK_SHIFT); // = 0b1111 pub const CHUNK_MASK: i32 = !(!0 << CHUNK_SHIFT); // = 0b1111
pub const CHUNK_LENGTH: usize = 1 << CHUNK_SHIFT; // = 16 pub const CHUNK_LENGTH: usize = 1 << CHUNK_SHIFT; // = 16
pub const CHUNK_SIZE: USize3 =
USize3::splat(unsafe { NonZeroU32::new_unchecked(CHUNK_LENGTH as u32) });
pub const CHUNK_MAX: IVec3 = IVec3::splat((CHUNK_LENGTH - 1) as i32); pub const CHUNK_MAX: IVec3 = IVec3::splat((CHUNK_LENGTH - 1) as i32);
#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Debug)] #[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Debug)]

@ -30,6 +30,14 @@ impl ChunkRegion {
Self::new_unchecked(min, max) Self::new_unchecked(min, max)
} }
pub fn min(&self) -> ChunkPos {
self.min
}
pub fn max(&self) -> ChunkPos {
self.max
}
pub fn size(&self) -> USize3 { pub fn size(&self) -> USize3 {
(self.max - self.min + IVec3::ONE) (self.max - self.min + IVec3::ONE)
.as_uvec3() .as_uvec3()

@ -10,7 +10,7 @@ pub struct USize3 {
} }
impl USize3 { impl USize3 {
pub fn new(width: NonZeroU32, height: NonZeroU32, depth: NonZeroU32) -> Self { pub const fn new(width: NonZeroU32, height: NonZeroU32, depth: NonZeroU32) -> Self {
Self { Self {
width, width,
height, height,
@ -18,7 +18,7 @@ impl USize3 {
} }
} }
pub fn splat(length: NonZeroU32) -> Self { pub const fn splat(length: NonZeroU32) -> Self {
Self::new(length, length, length) Self::new(length, length, length)
} }

@ -1,7 +1,9 @@
use bevy::{platform::collections::HashMap, prelude::*}; use std::ops::Deref;
use bevy::prelude::*;
use crate::{ use crate::{
bloxel::{block::BlockTexture, prelude::*}, bloxel::{block::BlockTexture, prelude::*, storage::ChunkedBloxelView, worldgen::Final},
TerrainAtlas, TerrainMaterial, TerrainAtlas, TerrainMaterial,
}; };
@ -16,12 +18,12 @@ pub fn generate_chunk_mesh(
// Allows us to find chunks with have their data filled in by // Allows us to find chunks with have their data filled in by
// the world generator, but don't yet have a mesh generated for it. // the world generator, but don't yet have a mesh generated for it.
chunks_without_meshes: Query<(Entity, &ChunkPos, &ChunkData), (With<Chunk>, Without<Mesh3d>)>, chunks_without_meshes: Query<(Entity, &ChunkPos, &Final), (With<Chunk>, Without<Mesh3d>)>,
// The `chunk_map` and `chunks_with_data` will allow us to get neighboring // The `chunk_map` and `chunks_with_data` will allow us to get neighboring
// chunks' data so we can determine whether blocks at the border should // chunks' data so we can determine whether blocks at the border should
// have their sides be culled. // have their sides be culled.
chunk_map: Single<&ChunkMap>, chunk_map: Single<&ChunkMap>,
chunks_with_data: Query<&ChunkData>, chunks_with_data: Query<&Final>,
terrain_material: Option<Res<TerrainMaterial>>, terrain_material: Option<Res<TerrainMaterial>>,
terrain_atlas: Option<Res<TerrainAtlas>>, terrain_atlas: Option<Res<TerrainAtlas>>,
@ -39,12 +41,10 @@ pub fn generate_chunk_mesh(
let terrain_atlas = terrain_atlas.unwrap(); let terrain_atlas = terrain_atlas.unwrap();
let terrain_atlas_layout = atlas_layouts.get(&terrain_atlas.layout).unwrap(); let terrain_atlas_layout = atlas_layouts.get(&terrain_atlas.layout).unwrap();
'chunks: for (entity, pos, storage) in chunks_without_meshes.iter() { 'chunks: for (entity, pos, data) in chunks_without_meshes.iter() {
let mut chunks = ChunkedBloxelView { let region = pos.to_block_region().expand(1).unwrap();
region: pos.to_block_region().expand(1).unwrap(), let mut chunks = ChunkedBloxelView::new(region);
map: HashMap::with_capacity(9), chunks.insert(*pos, data.deref());
};
chunks.map.insert(*pos, storage);
for pos in Neighbor::FACE.map(|n| pos + n) { for pos in Neighbor::FACE.map(|n| pos + n) {
let Some(neighbor_chunk) = chunk_map.get(&pos) else { let Some(neighbor_chunk) = chunk_map.get(&pos) else {
@ -53,7 +53,7 @@ pub fn generate_chunk_mesh(
let Some(neighbor_data) = chunks_with_data.get(*neighbor_chunk).ok() else { let Some(neighbor_data) = chunks_with_data.get(*neighbor_chunk).ok() else {
continue 'chunks; continue 'chunks;
}; };
chunks.map.insert(pos, neighbor_data); chunks.insert(pos, neighbor_data);
} }
let mesh = create_bloxel_mesh( let mesh = create_bloxel_mesh(
@ -69,25 +69,3 @@ pub fn generate_chunk_mesh(
)); ));
} }
} }
struct ChunkedBloxelView<'a> {
region: BlockRegion,
map: HashMap<ChunkPos, &'a ChunkData>,
}
impl ChunkedBloxelView<'_> {
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 BloxelStore<Entity> for ChunkedBloxelView<'_> {
fn size(&self) -> USize3 {
self.region.size()
}
fn get(&self, pos: IVec3) -> Option<Entity> {
ChunkedBloxelView::get(self, self.region.min() + pos)
}
}

@ -4,6 +4,7 @@ use ndarray::Array3;
use super::{BloxelStore, BloxelStoreMut}; use super::{BloxelStore, BloxelStoreMut};
use crate::bloxel::math::USize3; use crate::bloxel::math::USize3;
#[derive(Clone)]
pub struct BloxelArray<T: Copy> { pub struct BloxelArray<T: Copy> {
size: USize3, size: USize3,
data: Array3<T>, data: Array3<T>,

@ -0,0 +1,90 @@
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,10 +1,12 @@
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::*;

@ -3,6 +3,7 @@ use bevy::math::IVec3;
use super::{palette_storage::PaletteStorage, BloxelStore, BloxelStoreMut}; use super::{palette_storage::PaletteStorage, BloxelStore, BloxelStoreMut};
use crate::bloxel::math::USize3; use crate::bloxel::math::USize3;
#[derive(Clone)]
pub struct PaletteBloxelStorage<T: Copy + Eq> { pub struct PaletteBloxelStorage<T: Copy + Eq> {
size: USize3, size: USize3,
data: PaletteStorage<T>, data: PaletteStorage<T>,

@ -1,5 +1,6 @@
use bitvec::prelude::*; use bitvec::prelude::*;
#[derive(Clone)]
pub struct PaletteStorage<T: Copy + Eq> { pub struct PaletteStorage<T: Copy + Eq> {
len: usize, len: usize,
bits: usize, bits: usize,
@ -195,6 +196,7 @@ enum FindResult {
} }
// #[derive(Default)] // #[derive(Default)]
#[derive(Clone)]
struct PaletteEntry<T> { struct PaletteEntry<T> {
value: Option<T>, value: Option<T>,
used: usize, used: usize,

@ -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,17 @@
use bevy::prelude::*; use bevy::prelude::*;
use noise_functions::{Noise, Simplex};
use crate::{ mod chunk_loading;
bloxel::{ mod terrain_shape;
prelude::*, mod top_layer;
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; use top_layer::*;
pub use terrain_shape::TerrainShape;
pub use top_layer::TopLayer;
pub type Final = TopLayer;
pub struct WorldGenPlugin; pub struct WorldGenPlugin;
@ -21,143 +20,12 @@ 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,
generate_top_layer,
)
.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,45 @@
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);
}
}

@ -0,0 +1,70 @@
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…
Cancel
Save