Simple probably inefficient multi-step worldgen

main
copygirl 5 days ago
parent 958907228d
commit 5013ded7c9
  1. 16
      src/bloxel/chunk.rs
  2. 44
      src/bloxel/mesh/mod.rs
  3. 90
      src/bloxel/storage/chunked_bloxel_view.rs
  4. 2
      src/bloxel/storage/mod.rs
  5. 8
      src/bloxel/worldgen/mod.rs
  6. 17
      src/bloxel/worldgen/terrain_shape.rs
  7. 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,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)
}
}

@ -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::*;

@ -2,9 +2,16 @@ use bevy::prelude::*;
mod chunk_loading; mod chunk_loading;
mod terrain_shape; mod terrain_shape;
mod top_layer;
use chunk_loading::*; use chunk_loading::*;
use terrain_shape::*; use terrain_shape::*;
use top_layer::*;
pub use terrain_shape::TerrainShape;
pub use top_layer::TopLayer;
pub type Final = TopLayer;
pub struct WorldGenPlugin; pub struct WorldGenPlugin;
@ -16,6 +23,7 @@ impl Plugin for WorldGenPlugin {
destroy_chunks_away_from_camera, destroy_chunks_away_from_camera,
create_chunks_around_camera, create_chunks_around_camera,
generate_terrain_shape, generate_terrain_shape,
generate_top_layer,
) )
.chain(), .chain(),
); );

@ -1,21 +1,26 @@
use bevy::prelude::*; use bevy::prelude::*;
use noise_functions::{Noise, Simplex}; 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<Entity>);
pub fn generate_terrain_shape( pub fn generate_terrain_shape(
mut commands: Commands, mut commands: Commands,
terrain_blocks: Option<Res<TerrainBlocks>>, terrain_blocks: Option<Res<TerrainBlocks>>,
chunks_without_data: Query<(Entity, &ChunkPos), (With<Chunk>, Without<ChunkData>)>, chunks_without_data: Query<(Entity, &ChunkPos), (With<Chunk>, Without<TerrainShape>)>,
) { ) {
let Some(terrain) = terrain_blocks else { let Some(terrain) = terrain_blocks else {
return; return;
}; };
let slices = [terrain.rock, terrain.dirt, terrain.grass, terrain.sand];
for (entity, chunk_pos) in chunks_without_data.iter() { 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, _| { data.update(|relative, _| {
let block_pos = chunk_pos.to_block_pos(relative); let block_pos = chunk_pos.to_block_pos(relative);
@ -29,7 +34,7 @@ pub fn generate_terrain_shape(
.sample3(float_pos); .sample3(float_pos);
if sample > bias { if sample > bias {
slices[relative.y as usize % slices.len()] terrain.rock
} else { } else {
terrain.air terrain.air
} }

@ -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