From 5013ded7c957f1aa7e29eeba320577c982f93461 Mon Sep 17 00:00:00 2001 From: copygirl Date: Tue, 3 Jun 2025 13:47:42 +0200 Subject: [PATCH] Simple probably inefficient multi-step worldgen --- src/bloxel/chunk.rs | 16 +--- src/bloxel/mesh/mod.rs | 44 +++-------- src/bloxel/storage/chunked_bloxel_view.rs | 90 +++++++++++++++++++++++ src/bloxel/storage/mod.rs | 2 + src/bloxel/worldgen/mod.rs | 8 ++ src/bloxel/worldgen/terrain_shape.rs | 17 +++-- src/bloxel/worldgen/top_layer.rs | 70 ++++++++++++++++++ 7 files changed, 193 insertions(+), 54 deletions(-) create mode 100644 src/bloxel/storage/chunked_bloxel_view.rs create mode 100644 src/bloxel/worldgen/top_layer.rs diff --git a/src/bloxel/chunk.rs b/src/bloxel/chunk.rs index a1848bf..94fd370 100644 --- a/src/bloxel/chunk.rs +++ b/src/bloxel/chunk.rs @@ -1,24 +1,10 @@ use bevy::{platform::collections::HashMap, prelude::*}; -use super::{ - math::{ChunkPos, USize3, CHUNK_LENGTH}, - storage::PaletteBloxelStorage, -}; +use crate::bloxel::math::ChunkPos; #[derive(Component, Default)] pub struct Chunk; -#[derive(Component, Deref, DerefMut)] -#[require(Chunk)] -pub struct ChunkData(PaletteBloxelStorage); - -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)] #[require(Transform, Visibility)] pub struct ChunkMap(HashMap); diff --git a/src/bloxel/mesh/mod.rs b/src/bloxel/mesh/mod.rs index 58b0aea..7dca29a 100644 --- a/src/bloxel/mesh/mod.rs +++ b/src/bloxel/mesh/mod.rs @@ -1,7 +1,9 @@ -use bevy::{platform::collections::HashMap, prelude::*}; +use std::ops::Deref; + +use bevy::prelude::*; use crate::{ - bloxel::{block::BlockTexture, prelude::*}, + bloxel::{block::BlockTexture, prelude::*, storage::ChunkedBloxelView, worldgen::Final}, TerrainAtlas, TerrainMaterial, }; @@ -16,12 +18,12 @@ pub fn generate_chunk_mesh( // 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. - chunks_without_meshes: Query<(Entity, &ChunkPos, &ChunkData), (With, Without)>, + chunks_without_meshes: Query<(Entity, &ChunkPos, &Final), (With, Without)>, // 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 // have their sides be culled. chunk_map: Single<&ChunkMap>, - chunks_with_data: Query<&ChunkData>, + chunks_with_data: Query<&Final>, terrain_material: Option>, terrain_atlas: Option>, @@ -39,12 +41,10 @@ pub fn generate_chunk_mesh( let terrain_atlas = terrain_atlas.unwrap(); let terrain_atlas_layout = atlas_layouts.get(&terrain_atlas.layout).unwrap(); - 'chunks: for (entity, pos, storage) in chunks_without_meshes.iter() { - let mut chunks = ChunkedBloxelView { - region: pos.to_block_region().expand(1).unwrap(), - map: HashMap::with_capacity(9), - }; - chunks.map.insert(*pos, storage); + 'chunks: for (entity, pos, data) in chunks_without_meshes.iter() { + let region = pos.to_block_region().expand(1).unwrap(); + let mut chunks = ChunkedBloxelView::new(region); + chunks.insert(*pos, data.deref()); for pos in Neighbor::FACE.map(|n| pos + n) { 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 { continue 'chunks; }; - chunks.map.insert(pos, neighbor_data); + chunks.insert(pos, neighbor_data); } let mesh = create_bloxel_mesh( @@ -69,25 +69,3 @@ pub fn generate_chunk_mesh( )); } } - -struct ChunkedBloxelView<'a> { - region: BlockRegion, - map: HashMap, -} - -impl ChunkedBloxelView<'_> { - fn get(&self, pos: BlockPos) -> Option { - let (chunk_pos, rel_pos) = ChunkPos::from_block_pos(pos); - self.map.get(&chunk_pos).and_then(|d| d.get(rel_pos)) - } -} - -impl BloxelStore for ChunkedBloxelView<'_> { - fn size(&self) -> USize3 { - self.region.size() - } - - fn get(&self, pos: IVec3) -> Option { - ChunkedBloxelView::get(self, self.region.min() + pos) - } -} diff --git a/src/bloxel/storage/chunked_bloxel_view.rs b/src/bloxel/storage/chunked_bloxel_view.rs new file mode 100644 index 0000000..60dc4b7 --- /dev/null +++ b/src/bloxel/storage/chunked_bloxel_view.rs @@ -0,0 +1,90 @@ +use bevy::{platform::collections::HashMap, prelude::*}; + +use crate::bloxel::prelude::*; + +pub struct ChunkedBloxelView<'a, T: BloxelStore> { + region: BlockRegion, + map: HashMap, +} + +impl<'a, T: BloxelStore> 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 { + let (chunk_pos, rel_pos) = ChunkPos::from_block_pos(pos); + self.map.get(&chunk_pos).and_then(|d| d.get(rel_pos)) + } +} + +impl> BloxelStore for ChunkedBloxelView<'_, T> { + fn size(&self) -> USize3 { + self.region.size() + } + + fn get(&self, pos: IVec3) -> Option { + ChunkedBloxelView::get(self, self.region.min() + pos) + } +} + +pub struct ChunkedBloxelViewSingleMut<'a, T: BloxelStore, M: BloxelStoreMut> { + region: BlockRegion, + main: (ChunkPos, &'a mut M), + map: HashMap, +} + +impl<'a, T: BloxelStore, M: BloxelStoreMut> 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 { + 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 { + 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, M: BloxelStoreMut> BloxelStore + for ChunkedBloxelViewSingleMut<'a, T, M> +{ + fn size(&self) -> USize3 { + self.region.size() + } + + fn get(&self, pos: IVec3) -> Option { + ChunkedBloxelViewSingleMut::get(self, self.region.min() + pos) + } +} + +impl<'a, T: BloxelStore, M: BloxelStoreMut> BloxelStoreMut + for ChunkedBloxelViewSingleMut<'a, T, M> +{ + fn set(&mut self, pos: IVec3, value: Entity) -> Result { + ChunkedBloxelViewSingleMut::set(self, self.region.min() + pos, value) + } +} diff --git a/src/bloxel/storage/mod.rs b/src/bloxel/storage/mod.rs index 8ec49ea..43e7237 100644 --- a/src/bloxel/storage/mod.rs +++ b/src/bloxel/storage/mod.rs @@ -1,10 +1,12 @@ mod bloxel_array; mod bloxel_store; +mod chunked_bloxel_view; mod chunked_octree; mod palette_bloxel_storage; mod palette_storage; pub use bloxel_array::*; pub use bloxel_store::*; +pub use chunked_bloxel_view::*; pub use chunked_octree::*; pub use palette_bloxel_storage::*; diff --git a/src/bloxel/worldgen/mod.rs b/src/bloxel/worldgen/mod.rs index 8aa0ebb..d30efb8 100644 --- a/src/bloxel/worldgen/mod.rs +++ b/src/bloxel/worldgen/mod.rs @@ -2,9 +2,16 @@ use bevy::prelude::*; mod chunk_loading; mod terrain_shape; +mod top_layer; use chunk_loading::*; use terrain_shape::*; +use top_layer::*; + +pub use terrain_shape::TerrainShape; +pub use top_layer::TopLayer; + +pub type Final = TopLayer; pub struct WorldGenPlugin; @@ -16,6 +23,7 @@ impl Plugin for WorldGenPlugin { destroy_chunks_away_from_camera, create_chunks_around_camera, generate_terrain_shape, + generate_top_layer, ) .chain(), ); diff --git a/src/bloxel/worldgen/terrain_shape.rs b/src/bloxel/worldgen/terrain_shape.rs index f31cdc7..a56fe9e 100644 --- a/src/bloxel/worldgen/terrain_shape.rs +++ b/src/bloxel/worldgen/terrain_shape.rs @@ -1,21 +1,26 @@ use bevy::prelude::*; use noise_functions::{Noise, Simplex}; -use crate::{bloxel::prelude::*, TerrainBlocks}; +use crate::{ + bloxel::{prelude::*, storage::PaletteBloxelStorage}, + TerrainBlocks, +}; + +#[derive(Component, Deref, DerefMut)] +#[require(Chunk)] +pub struct TerrainShape(PaletteBloxelStorage); pub fn generate_terrain_shape( mut commands: Commands, terrain_blocks: Option>, - chunks_without_data: Query<(Entity, &ChunkPos), (With, Without)>, + chunks_without_data: Query<(Entity, &ChunkPos), (With, Without)>, ) { 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); + let mut data = TerrainShape(PaletteBloxelStorage::new(CHUNK_SIZE, terrain.air)); data.update(|relative, _| { let block_pos = chunk_pos.to_block_pos(relative); @@ -29,7 +34,7 @@ pub fn generate_terrain_shape( .sample3(float_pos); if sample > bias { - slices[relative.y as usize % slices.len()] + terrain.rock } else { terrain.air } diff --git a/src/bloxel/worldgen/top_layer.rs b/src/bloxel/worldgen/top_layer.rs new file mode 100644 index 0000000..b6d5a16 --- /dev/null +++ b/src/bloxel/worldgen/top_layer.rs @@ -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); + +pub fn generate_top_layer( + mut commands: Commands, + terrain_blocks: Option>, + + mut chunks_without_top_layer: Query< + (Entity, &ChunkPos, &TerrainShape), + (With, Without), + >, + + 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); + } +}