From 7d5b706d0d200aa1b32acc59b694ce36311562a3 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Thu, 2 May 2024 12:42:14 +0200 Subject: [PATCH 1/8] rewrite worldgen --- kubi-shared/src/worldgen.rs | 378 ++++-------------------- kubi-shared/src/worldgen/_01_terrain.rs | 28 ++ 2 files changed, 86 insertions(+), 320 deletions(-) create mode 100644 kubi-shared/src/worldgen/_01_terrain.rs diff --git a/kubi-shared/src/worldgen.rs b/kubi-shared/src/worldgen.rs index 7ddb678..958ffb1 100644 --- a/kubi-shared/src/worldgen.rs +++ b/kubi-shared/src/worldgen.rs @@ -1,342 +1,80 @@ -use bracket_noise::prelude::*; -use rand::prelude::*; -use glam::{IVec3, ivec3, Vec3Swizzles, IVec2}; -use rand_xoshiro::Xoshiro256StarStar; +use std::sync::atomic::AtomicBool; + +use glam::{ivec3, IVec3}; use crate::{ - chunk::{BlockData, CHUNK_SIZE}, block::Block, + chunk::{BlockData, CHUNK_SIZE}, queue::QueuedBlock, }; -fn mountain_ramp(mut x: f32) -> f32 { - x *= 2.0; - if x < 0.4 { - 0.5 * x - } else if x < 0.55 { - 4. * (x - 0.4) + 0.2 - } else { - 0.4444 * (x - 0.55) + 0.8 +mod _01_terrain; + +trait WorldGenStep { + fn initialize(generator: &WorldGenerator) -> Self; + fn generate(&mut self, generator: &mut WorldGenerator); +} + +macro_rules! run_steps { + ($gen: expr, $abort: expr, [$($step:ty),* $(,)?]) => { + { + let _abort: AtomicBool = $abort.unwrap_or(AtomicBool::new(false)); + $({ + let _ensure_ref: &mut WorldGenerator = $gen; + struct _Ensure0(T); + type _Ensure1 = _Ensure0<$step>; + let mut step: _Ensure1 = _Ensure0(<$step>::initialize(&*_ensure_ref)); + step.0.generate(_ensure_ref); + })* + } + }; +} + +pub struct WorldGenerator { + seed: u64, + chunk_position: IVec3, + blocks: BlockData, + queue: Vec, +} + +impl WorldGenerator { + fn offset(&self) -> IVec3 { + self.chunk_position * CHUNK_SIZE as i32 } -} -fn local_height(height: i32, chunk_position: IVec3) -> usize { - let offset = chunk_position * CHUNK_SIZE as i32; - (height - offset.y).clamp(0, CHUNK_SIZE as i32) as usize -} - -fn local_y_position(height: i32, chunk_position: IVec3) -> Option { - let offset = chunk_position * CHUNK_SIZE as i32; - let position = height - offset.y; - (0..CHUNK_SIZE as i32).contains(&position).then_some(position as usize) -} - - -pub fn generate_world(chunk_position: IVec3, seed: u64) -> (BlockData, Vec) { - let offset = chunk_position * CHUNK_SIZE as i32; - let mut blocks = Box::new([[[Block::Air; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]); - let mut queue = Vec::with_capacity(0); - - let mut smart_place = |blocks: &mut BlockData, position: IVec3, block: Block| { + fn place_or_queue(&mut self, position: IVec3, block: Block) { + let offset = self.offset(); if position.to_array().iter().any(|&x| !(0..CHUNK_SIZE).contains(&(x as usize))) { let event_pos = offset + position; - queue.retain(|block: &QueuedBlock| { + self.queue.retain(|block: &QueuedBlock| { block.position != event_pos }); - queue.push(QueuedBlock { - position: event_pos, + self.queue.push(QueuedBlock { + position: event_pos, block_type: block, soft: true }); } else { - blocks[position.x as usize][position.y as usize][position.z as usize] = block; - } - }; - - //STICK - if chunk_position.x == 0 && chunk_position.y == 5 { - for z in 0..CHUNK_SIZE { - blocks[0][0][z] = Block::Stone; - } - } - // - - let mut height_noise = FastNoise::seeded(seed); - height_noise.set_fractal_type(FractalType::FBM); - height_noise.set_fractal_octaves(4); - height_noise.set_frequency(0.003); - - let mut elevation_noise = FastNoise::seeded(seed.rotate_left(1)); - elevation_noise.set_fractal_type(FractalType::FBM); - elevation_noise.set_fractal_octaves(1); - elevation_noise.set_frequency(0.001); - - let mut cave_noise_a = FastNoise::seeded(seed.rotate_left(2)); - cave_noise_a.set_fractal_type(FractalType::FBM); - cave_noise_a.set_fractal_octaves(2); - cave_noise_a.set_frequency(0.01); - - let mut cave_noise_b = FastNoise::seeded(seed.rotate_left(3)); - cave_noise_b.set_fractal_type(FractalType::FBM); - cave_noise_b.set_fractal_octaves(3); - cave_noise_b.set_frequency(0.015); - - let mut cave_noise_holes = FastNoise::seeded(seed.rotate_left(4)); - cave_noise_holes.set_fractal_type(FractalType::FBM); - cave_noise_holes.set_fractal_octaves(2); - cave_noise_holes.set_frequency(0.005); - - let mut ravine_nose_line = FastNoise::seeded(seed.rotate_left(5)); - ravine_nose_line.set_fractal_type(FractalType::Billow); - ravine_nose_line.set_fractal_octaves(2); - ravine_nose_line.set_frequency(0.005); - - let mut ravine_noise_location = FastNoise::seeded(seed.rotate_left(6)); - ravine_noise_location.set_fractal_type(FractalType::FBM); - ravine_noise_location.set_fractal_octaves(1); - ravine_noise_location.set_frequency(0.005); - - let mut river_noise = FastNoise::seeded(seed.rotate_left(7)); - river_noise.set_fractal_type(FractalType::Billow); - river_noise.set_fractal_octaves(2); - river_noise.set_frequency(0.5 * 0.005); - - let mut rng = Xoshiro256StarStar::seed_from_u64( - seed - ^ (chunk_position.x as u32 as u64) - ^ ((chunk_position.z as u32 as u64) << 32) - ); - let rng_map_a: [[f32; CHUNK_SIZE]; CHUNK_SIZE] = rng.gen(); - let rng_map_b: [[f32; CHUNK_SIZE]; CHUNK_SIZE] = rng.gen(); - - //Generate height map - let mut within_heightmap = false; - let mut deco_heightmap = [[None; CHUNK_SIZE]; CHUNK_SIZE]; - - for x in 0..CHUNK_SIZE { - for z in 0..CHUNK_SIZE { - let (noise_x, noise_y) = ((offset.x + x as i32) as f32, (offset.z + z as i32) as f32); - //sample noises (that are needed right now) - let raw_heightmap_value = height_noise.get_noise(noise_x, noise_y); - let raw_elevation_value = elevation_noise.get_noise(noise_x, noise_y); - let raw_ravine_location_value = ravine_noise_location.get_noise(noise_x, noise_y); - //compute height - let mut is_surface = true; - let mut river_fill_height = None; - let height = { - let local_elevation = raw_elevation_value.powi(4).sqrt(); - let mut height = (mountain_ramp(raw_heightmap_value) * local_elevation * 100.) as i32; - //Flatten valleys - if height < 0 { - height /= 2; - } - //Generate rivers - { - let river_width = (height as f32 / -5.).clamp(0.5, 1.); - let river_value = river_noise.get_noise(noise_x, noise_y); - if ((-0.00625 * river_width)..(0.00625 * river_width)).contains(&(river_value.powi(2))) { - is_surface = false; - river_fill_height = Some(height - 1); - //river_fill_height = Some(-3); - height -= (river_width * 15. * ((0.00625 * river_width) - river_value.powi(2)) * (1. / (0.00625 * river_width))).round() as i32; - } - } - //Generate ravines - if height < 0 && raw_ravine_location_value > 0.4 { - let raw_ravine_value = ravine_nose_line.get_noise(noise_x, noise_y); - if (-0.0125..0.0125).contains(&(raw_ravine_value.powi(2))) { - is_surface = false; - height -= (100. * (0.0125 - raw_ravine_value.powi(2)) * (1. / 0.0125)).round() as i32; - } - } - height - }; - //add to heightmap - if is_surface { - deco_heightmap[x][z] = Some(height); - //place dirt - for y in 0..local_height(height, chunk_position) { - blocks[x][y][z] = Block::Dirt; - within_heightmap = true; - } - //place stone - for y in 0..local_height(height - 5 - (raw_heightmap_value * 5.) as i32, chunk_position) { - blocks[x][y][z] = Block::Stone; - within_heightmap = true; - } - //place grass - if let Some(y) = local_y_position(height, chunk_position) { - blocks[x][y][z] = Block::Grass; - within_heightmap = true; - } - } else if let Some(river_fill_height) = river_fill_height { - //Place water - for y in 0..local_height(river_fill_height, chunk_position) { - blocks[x][y][z] = Block::Water; - within_heightmap = true; - } - //Place stone - for y in 0..local_height(height, chunk_position) { - blocks[x][y][z] = Block::Stone; - within_heightmap = true; - } - //Place dirt - if let Some(y) = local_y_position(height, chunk_position) { - blocks[x][y][z] = Block::Dirt; - within_heightmap = true; - } - } else { - //Place stone - for y in 0..local_height(height, chunk_position) { - blocks[x][y][z] = Block::Stone; - within_heightmap = true; - } - } - } - } - - //Carve out caves - if within_heightmap { - for z in 0..CHUNK_SIZE { - for y in 0..CHUNK_SIZE { - for x in 0..CHUNK_SIZE { - if blocks[x][y][z] != Block::Stone { continue } - - let cave_size = ((offset.y + y as i32) as f32 / -100.).clamp(0., 1.); - let inv_cave_size = 1. - cave_size; - if cave_size < 0.1 { continue } - - let position = ivec3(x as i32, y as i32, z as i32) + offset; - - let is_cave = || { - let raw_cavemap_value_a = cave_noise_a.get_noise3d(position.x as f32, position.y as f32, position.z as f32); - let raw_cavemap_value_b = cave_noise_b.get_noise3d(position.x as f32, position.y as f32, position.z as f32); - ((cave_size * -0.15)..=(cave_size * 0.15)).contains(&raw_cavemap_value_a) && - ((cave_size * -0.15)..=(cave_size * 0.15)).contains(&raw_cavemap_value_b) - }; - let is_hole_cave = || { - let raw_cavemap_value_holes = cave_noise_holes.get_noise3d(position.x as f32, position.y as f32, position.z as f32); - ((0.9 + (0.1 * inv_cave_size))..=1.0).contains(&raw_cavemap_value_holes.abs()) - }; - - if is_cave() || is_hole_cave() { - blocks[x][y][z] = Block::Air; - if deco_heightmap[x][z] == Some(y as i32 + offset.y) { - deco_heightmap[x][z] = None - } - } - } - } + self.blocks[position.x as usize][position.y as usize][position.z as usize] = block; } } - //Add decorations - for x in 0..CHUNK_SIZE { - for z in 0..CHUNK_SIZE { - //get height - let Some(height) = deco_heightmap[x][z] else { continue }; - //check for air - // if blocks[x][local_y][z] == Block::Air { - // continue - // } - //place tall grass - if rng_map_a[x][z] < 0.03 { - if let Some(y) = local_y_position(height + 1, chunk_position) { - blocks[x][y][z] = Block::TallGrass; - } - } - //place trees! - if rng_map_a[x][z] < 0.001 { - //Replace grass with dirt under the tree - if let Some(y) = local_y_position(height, chunk_position) { - blocks[x][y][z] = Block::Dirt; - } - - //Place wood (no smart_place needed here!) - let tree_height = 4 + (rng_map_b[x][z] * 3.).round() as i32; - for tree_y in 0..tree_height { - if let Some(y) = local_y_position(height + 1 + tree_y, chunk_position) { - blocks[x][y][z] = Block::Wood; - } - } - - let tree_height = 4 + (rng_map_b[x][z] * 3.).round() as i32; - - //Place leaf blocks - if let Some(y) = local_y_position(height + 1, chunk_position) { - let tree_pos = ivec3(x as i32, y as i32, z as i32); - // Place wood (smart_place) - // for tree_y in 0..tree_height { - // smart_place(&mut blocks, tree_pos + tree_y * IVec3::Y, Block::Wood); - // } - // Part that wraps around the tree - { - let tree_leaf_height = tree_height - 3; - let leaf_width = 2; - for tree_y in tree_leaf_height..tree_height { - for tree_x in (-leaf_width)..=leaf_width { - for tree_z in (-leaf_width)..=leaf_width { - let tree_offset = ivec3(tree_x, tree_y, tree_z); - if tree_offset.xz() == IVec2::ZERO { continue } - smart_place(&mut blocks, tree_pos + tree_offset, Block::Leaf); - } - } - } - } - //part above the tree - { - let leaf_above_height = 2; - let leaf_width = 1; - for tree_y in tree_height..(tree_height + leaf_above_height) { - for tree_x in (-leaf_width)..=leaf_width { - for tree_z in (-leaf_width)..=leaf_width { - let tree_offset = ivec3(tree_x, tree_y, tree_z); - smart_place(&mut blocks, tree_pos + tree_offset, Block::Leaf); - } - } - } - } - } - } + pub fn new(chunk_position: IVec3, seed: u64) -> Self { + Self { + seed, + chunk_position, + blocks: Box::new([[[Block::Air; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]), + queue: Vec::with_capacity(0), } } - (blocks, queue) - - // let mut cave_noise = FastNoise::seeded(seed); - // cave_noise.set_fractal_type(FractalType::FBM); - // cave_noise.set_frequency(0.1); - - // let mut dirt_noise = FastNoise::seeded(seed.rotate_left(1)); - // dirt_noise.set_fractal_type(FractalType::FBM); - // dirt_noise.set_frequency(0.1); - - // - - // if chunk_position.y >= 0 { - // if chunk_position.y == 0 { - // for x in 0..CHUNK_SIZE { - // for z in 0..CHUNK_SIZE { - // blocks[x][0][z] = Block::Dirt; - // blocks[x][1][z] = Block::Grass; - // } - // } - // } - // } else { - // for x in 0..CHUNK_SIZE { - // for y in 0..CHUNK_SIZE { - // for z in 0..CHUNK_SIZE { - // let position = ivec3(x as i32, y as i32, z as i32) + offset; - // let v_cave_noise = cave_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32 - 10.0).clamp(0., 1.); - // let v_dirt_noise = dirt_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32).clamp(0., 1.); - // if v_cave_noise > 0.5 { - // blocks[x][y][z] = Block::Stone; - // } else if v_dirt_noise > 0.5 { - // blocks[x][y][z] = Block::Dirt; - // } - // } - // } - // } - // } - // blocks - + pub fn generate(mut self, abort: Option) -> (BlockData, Vec) { + run_steps!(&mut self, abort, [ + _01_terrain::TerrainStep + ]); + (self.blocks, self.queue) + } +} + +pub fn generate_world(chunk_position: IVec3, seed: u64) -> (BlockData, Vec) { + WorldGenerator::new(chunk_position, seed).generate(None) } diff --git a/kubi-shared/src/worldgen/_01_terrain.rs b/kubi-shared/src/worldgen/_01_terrain.rs new file mode 100644 index 0000000..98a07b3 --- /dev/null +++ b/kubi-shared/src/worldgen/_01_terrain.rs @@ -0,0 +1,28 @@ +use bracket_noise::prelude::{FastNoise, FractalType}; +use glam::ivec3; +use crate::{block::Block, chunk::CHUNK_SIZE}; +use super::{WorldGenerator, WorldGenStep}; + +pub struct TerrainStep { + noise: FastNoise, +} +impl WorldGenStep for TerrainStep { + fn initialize(generator: &WorldGenerator) -> Self { + let mut noise = FastNoise::seeded(generator.seed); + noise.set_fractal_type(FractalType::FBM); + noise.set_fractal_octaves(4); + noise.set_frequency(0.003); + Self { noise } + } + + fn generate(&mut self, generator: &mut WorldGenerator) { + for x in 0..CHUNK_SIZE as i32 { + for z in 0..CHUNK_SIZE as i32 { + let height = (self.noise.get_noise(x as f32, z as f32) * 8.0) as i32; + for y in 0..height { + generator.place_or_queue(ivec3(x, y, z), Block::Stone); + } + } + } + } +} From a3bc619cb648ae62335cedfa87a7a50cc7ae31c2 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Thu, 2 May 2024 16:50:46 +0200 Subject: [PATCH 2/8] abortions and stuff --- Cargo.lock | 26 +++++ kubi-server/src/world/tasks.rs | 120 ++++++++++++------------ kubi-shared/Cargo.toml | 3 +- kubi-shared/src/block.rs | 3 +- kubi-shared/src/worldgen.rs | 101 +++++++++++++++++--- kubi-shared/src/worldgen/_01_terrain.rs | 14 +-- kubi-shared/src/worldgen/_02_water.rs | 19 ++++ kubi-shared/src/worldgen/_03_caves.rs | 42 +++++++++ kubi/Cargo.toml | 1 + kubi/src/world/chunk.rs | 8 +- kubi/src/world/loading.rs | 43 +++++++-- kubi/src/world/tasks.rs | 14 ++- 12 files changed, 298 insertions(+), 96 deletions(-) create mode 100644 kubi-shared/src/worldgen/_02_water.rs create mode 100644 kubi-shared/src/worldgen/_03_caves.rs diff --git a/Cargo.lock b/Cargo.lock index 850c4e7..90612c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -262,6 +271,20 @@ name = "bytemuck" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] [[package]] name = "byteorder" @@ -1150,6 +1173,7 @@ version = "0.0.0" dependencies = [ "android-activity", "anyhow", + "atomic", "flume", "gilrs", "glam", @@ -1217,8 +1241,10 @@ name = "kubi-shared" version = "0.0.0" dependencies = [ "anyhow", + "atomic", "bincode", "bracket-noise", + "bytemuck", "glam", "hashbrown 0.14.3", "nohash-hasher", diff --git a/kubi-server/src/world/tasks.rs b/kubi-server/src/world/tasks.rs index 8094632..4e45c92 100644 --- a/kubi-server/src/world/tasks.rs +++ b/kubi-server/src/world/tasks.rs @@ -1,59 +1,61 @@ -use shipyard::{Unique, AllStoragesView}; -use flume::{unbounded, Sender, Receiver}; -use glam::IVec3; -use rayon::{ThreadPool, ThreadPoolBuilder}; -use anyhow::Result; -use kubi_shared::{ - chunk::BlockData, - worldgen::generate_world, - queue::QueuedBlock, -}; - -pub enum ChunkTask { - LoadChunk { - position: IVec3, - seed: u64, - } -} - -pub enum ChunkTaskResponse { - ChunkLoaded { - chunk_position: IVec3, - blocks: BlockData, - queue: Vec - } -} - -#[derive(Unique)] -pub struct ChunkTaskManager { - channel: (Sender, Receiver), - pool: ThreadPool, -} -impl ChunkTaskManager { - pub fn new() -> Result { - Ok(Self { - channel: unbounded(), - pool: ThreadPoolBuilder::new().build()? - }) - } - pub fn spawn_task(&self, task: ChunkTask) { - let sender = self.channel.0.clone(); - self.pool.spawn(move || { - sender.send(match task { - ChunkTask::LoadChunk { position: chunk_position, seed } => { - let (blocks, queue) = generate_world(chunk_position, seed); - ChunkTaskResponse::ChunkLoaded { chunk_position, blocks, queue } - } - }).unwrap() - }) - } - pub fn receive(&self) -> Option { - self.channel.1.try_recv().ok() - } -} - -pub fn init_chunk_task_manager( - storages: AllStoragesView -) { - storages.add_unique(ChunkTaskManager::new().expect("ChunkTaskManager Init failed")); -} +use shipyard::{Unique, AllStoragesView}; +use flume::{unbounded, Sender, Receiver}; +use glam::IVec3; +use rayon::{ThreadPool, ThreadPoolBuilder}; +use anyhow::Result; +use kubi_shared::{ + chunk::BlockData, + worldgen::generate_world, + queue::QueuedBlock, +}; + +pub enum ChunkTask { + LoadChunk { + position: IVec3, + seed: u64, + } +} + +pub enum ChunkTaskResponse { + ChunkLoaded { + chunk_position: IVec3, + blocks: BlockData, + queue: Vec + } +} + +#[derive(Unique)] +pub struct ChunkTaskManager { + channel: (Sender, Receiver), + pool: ThreadPool, +} +impl ChunkTaskManager { + pub fn new() -> Result { + Ok(Self { + channel: unbounded(), + pool: ThreadPoolBuilder::new().build()? + }) + } + pub fn spawn_task(&self, task: ChunkTask) { + let sender = self.channel.0.clone(); + self.pool.spawn(move || { + sender.send(match task { + ChunkTask::LoadChunk { position: chunk_position, seed } => { + let Some((blocks, queue)) = generate_world(chunk_position, seed, None) else { + return + }; + ChunkTaskResponse::ChunkLoaded { chunk_position, blocks, queue } + } + }).unwrap() + }) + } + pub fn receive(&self) -> Option { + self.channel.1.try_recv().ok() + } +} + +pub fn init_chunk_task_manager( + storages: AllStoragesView +) { + storages.add_unique(ChunkTaskManager::new().expect("ChunkTaskManager Init failed")); +} diff --git a/kubi-shared/Cargo.toml b/kubi-shared/Cargo.toml index 2a05a9f..63cf255 100644 --- a/kubi-shared/Cargo.toml +++ b/kubi-shared/Cargo.toml @@ -19,9 +19,10 @@ rand = { version = "0.8", default_features = false, features = ["std", "min_cons rand_xoshiro = "0.6" hashbrown = { version = "0.14", features = ["serde"] } nohash-hasher = "0.2" -#bytemuck = { version = "1.14", features = ["derive"] } +bytemuck = { version = "1.14", features = ["derive"] } static_assertions = "1.1" nz = "0.3" +atomic = "0.6" [features] default = [] diff --git a/kubi-shared/src/block.rs b/kubi-shared/src/block.rs index b176b0f..9849071 100644 --- a/kubi-shared/src/block.rs +++ b/kubi-shared/src/block.rs @@ -26,9 +26,10 @@ pub enum BlockTexture { Water, } -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, EnumIter, TryFromPrimitive)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, PartialEq, Eq, EnumIter, TryFromPrimitive)] #[repr(u8)] pub enum Block { + #[default] Air, Marker, Stone, diff --git a/kubi-shared/src/worldgen.rs b/kubi-shared/src/worldgen.rs index 958ffb1..772aeb1 100644 --- a/kubi-shared/src/worldgen.rs +++ b/kubi-shared/src/worldgen.rs @@ -1,6 +1,8 @@ -use std::sync::atomic::AtomicBool; - -use glam::{ivec3, IVec3}; +use std::sync::Arc; +use atomic::Atomic; +use bytemuck::{CheckedBitPattern, NoUninit}; +use glam::IVec3; +use static_assertions::const_assert; use crate::{ block::Block, chunk::{BlockData, CHUNK_SIZE}, @@ -8,6 +10,18 @@ use crate::{ }; mod _01_terrain; +mod _02_water; +mod _03_caves; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Default, NoUninit, CheckedBitPattern)] +pub enum AbortState { + #[default] + Continue, + Abort, + Aborted, +} +const_assert!(Atomic::::is_lock_free()); trait WorldGenStep { fn initialize(generator: &WorldGenerator) -> Self; @@ -16,16 +30,29 @@ trait WorldGenStep { macro_rules! run_steps { ($gen: expr, $abort: expr, [$($step:ty),* $(,)?]) => { - { - let _abort: AtomicBool = $abort.unwrap_or(AtomicBool::new(false)); + (||{ + let _abort: ::std::sync::Arc<::atomic::Atomic<$crate::worldgen::AbortState>> = + $abort.unwrap_or_else(|| ::std::sync::Arc::new(::atomic::Atomic::new($crate::worldgen::AbortState::Continue))); + + let _chkabt = || _abort.compare_exchange( + $crate::worldgen::AbortState::Abort, + $crate::worldgen::AbortState::Aborted, + ::atomic::Ordering::Relaxed, + ::atomic::Ordering::Relaxed + ).is_ok(); + $({ - let _ensure_ref: &mut WorldGenerator = $gen; - struct _Ensure0(T); + let _ensure_ref: &mut $crate::worldgen::WorldGenerator = $gen; + struct _Ensure0(T); type _Ensure1 = _Ensure0<$step>; let mut step: _Ensure1 = _Ensure0(<$step>::initialize(&*_ensure_ref)); + if _chkabt() { return false } step.0.generate(_ensure_ref); + if _chkabt() { return false } })* - } + + true + })() }; } @@ -41,6 +68,32 @@ impl WorldGenerator { self.chunk_position * CHUNK_SIZE as i32 } + fn query(&self, position: IVec3) -> Block { + // let offset = self.offset(); + // let event_pos = offset + position; + // if let Some(block) = self.queue.iter().find(|block| block.position == event_pos) { + // block.block_type + // } else { + // self.blocks[position.x as usize][position.y as usize][position.z as usize] + // } + self.blocks[position.x as usize][position.y as usize][position.z as usize] + } + + fn place(&mut self, position: IVec3, block: Block) { + // let offset = self.offset(); + // let event_pos = offset + position; + // self.queue.retain(|block: &QueuedBlock| { + // block.position != event_pos + // }); + self.blocks[position.x as usize][position.y as usize][position.z as usize] = block; + } + + fn place_if_empty(&mut self, position: IVec3, block: Block) { + if self.query(position) == Block::Air { + self.place(position, block); + } + } + fn place_or_queue(&mut self, position: IVec3, block: Block) { let offset = self.offset(); if position.to_array().iter().any(|&x| !(0..CHUNK_SIZE).contains(&(x as usize))) { @@ -58,6 +111,21 @@ impl WorldGenerator { } } + fn global_position(&self, position: IVec3) -> IVec3 { + self.offset() + position + } + + fn local_height(&self, height: i32) -> i32 { + let offset = self.chunk_position * CHUNK_SIZE as i32; + (height - offset.y).clamp(0, CHUNK_SIZE as i32) + } + + fn local_y_position(&self, y: i32) -> Option { + let offset = self.chunk_position * CHUNK_SIZE as i32; + let position = y - offset.y; + (0..CHUNK_SIZE as i32).contains(&position).then_some(position) + } + pub fn new(chunk_position: IVec3, seed: u64) -> Self { Self { seed, @@ -67,14 +135,19 @@ impl WorldGenerator { } } - pub fn generate(mut self, abort: Option) -> (BlockData, Vec) { + /// Generate the chunk. + /// + /// Will return `None` only if the generation was aborted. + pub fn generate(mut self, abort: Option>>) -> Option<(BlockData, Vec)> { run_steps!(&mut self, abort, [ - _01_terrain::TerrainStep - ]); - (self.blocks, self.queue) + _01_terrain::TerrainStep, + _02_water::WaterStep, + _03_caves::CaveStep, + ]).then_some((self.blocks, self.queue)) } } -pub fn generate_world(chunk_position: IVec3, seed: u64) -> (BlockData, Vec) { - WorldGenerator::new(chunk_position, seed).generate(None) +pub fn generate_world(chunk_position: IVec3, seed: u64, abort: Option>>) -> Option<(BlockData, Vec)> { + //TODO: pass through None for abort + WorldGenerator::new(chunk_position, seed).generate(abort) } diff --git a/kubi-shared/src/worldgen/_01_terrain.rs b/kubi-shared/src/worldgen/_01_terrain.rs index 98a07b3..9e21aa6 100644 --- a/kubi-shared/src/worldgen/_01_terrain.rs +++ b/kubi-shared/src/worldgen/_01_terrain.rs @@ -6,21 +6,23 @@ use super::{WorldGenerator, WorldGenStep}; pub struct TerrainStep { noise: FastNoise, } + impl WorldGenStep for TerrainStep { fn initialize(generator: &WorldGenerator) -> Self { let mut noise = FastNoise::seeded(generator.seed); - noise.set_fractal_type(FractalType::FBM); - noise.set_fractal_octaves(4); + noise.set_fractal_type(FractalType::RigidMulti); + noise.set_fractal_octaves(5); noise.set_frequency(0.003); Self { noise } } - fn generate(&mut self, generator: &mut WorldGenerator) { + fn generate(&mut self, gen: &mut WorldGenerator) { for x in 0..CHUNK_SIZE as i32 { for z in 0..CHUNK_SIZE as i32 { - let height = (self.noise.get_noise(x as f32, z as f32) * 8.0) as i32; - for y in 0..height { - generator.place_or_queue(ivec3(x, y, z), Block::Stone); + let global_xz = gen.global_position(ivec3(x, 0, z)); + let height = (self.noise.get_noise(global_xz.x as f32, global_xz.z as f32) * 32.0) as i32; + for y in 0..gen.local_height(height) { + gen.place(ivec3(x, y, z), Block::Stone); } } } diff --git a/kubi-shared/src/worldgen/_02_water.rs b/kubi-shared/src/worldgen/_02_water.rs new file mode 100644 index 0000000..136bc41 --- /dev/null +++ b/kubi-shared/src/worldgen/_02_water.rs @@ -0,0 +1,19 @@ +use bracket_noise::prelude::{FastNoise, FractalType}; +use glam::ivec3; +use crate::{block::Block, chunk::CHUNK_SIZE}; +use super::{WorldGenerator, WorldGenStep}; + +pub struct WaterStep; + +impl WorldGenStep for WaterStep { + fn initialize(_: &WorldGenerator) -> Self { Self } + fn generate(&mut self, gen: &mut WorldGenerator) { + for x in 0..CHUNK_SIZE as i32 { + for z in 0..CHUNK_SIZE as i32 { + for y in 0..gen.local_height(0) { + gen.place_if_empty(ivec3(x, y, z), Block::Water); + } + } + } + } +} diff --git a/kubi-shared/src/worldgen/_03_caves.rs b/kubi-shared/src/worldgen/_03_caves.rs new file mode 100644 index 0000000..5e6d73e --- /dev/null +++ b/kubi-shared/src/worldgen/_03_caves.rs @@ -0,0 +1,42 @@ +use bracket_noise::prelude::{FastNoise, FractalType}; +use glam::{ivec3, IVec3}; +use crate::{block::Block, chunk::CHUNK_SIZE}; +use super::{WorldGenStep, WorldGenerator}; + +pub struct CaveStep { + a: FastNoise, + b: FastNoise, +} + +impl WorldGenStep for CaveStep { + fn initialize(gen: &WorldGenerator) -> Self { + let mut a = FastNoise::seeded(gen.seed); + a.set_fractal_type(FractalType::FBM); + a.set_frequency(0.015); + + let mut b = FastNoise::seeded(gen.seed.rotate_left(1) + 1); + b.set_fractal_type(FractalType::FBM); + b.set_frequency(0.015); + + Self { a, b } + } + fn generate(&mut self, gen: &mut WorldGenerator) { + for x in 0..CHUNK_SIZE as i32 { + for z in 0..CHUNK_SIZE as i32 { + for y in 0..CHUNK_SIZE as i32 { + let pos: IVec3 = ivec3(x, y, z); + if gen.query(pos) != Block::Stone { continue } + + let gpos = gen.global_position(pos); + let noise_a = self.a.get_noise3d(gpos.x as f32, gpos.y as f32, gpos.z as f32); + let noise_b = self.b.get_noise3d(gpos.x as f32, gpos.y as f32, gpos.z as f32); + let noise_min = noise_a.min(noise_b); + + if noise_min > 0.5 { return } + + gen.place(ivec3(x, y, z), Block::Air); + } + } + } + } +} diff --git a/kubi/Cargo.toml b/kubi/Cargo.toml index 5b54d87..ed59a2c 100644 --- a/kubi/Cargo.toml +++ b/kubi/Cargo.toml @@ -37,6 +37,7 @@ static_assertions = "1.1" tinyset = "0.4" serde_json = { version = "1.0", optional = true } #only used for `generate_visualizer_data` rand = { version = "0.8", features = ["alloc", "small_rng"]} +atomic = "0.6" [target.'cfg(target_os = "android")'.dependencies] android-activity = "^0.5.2" diff --git a/kubi/src/world/chunk.rs b/kubi/src/world/chunk.rs index 07b2049..e8ddd3a 100644 --- a/kubi/src/world/chunk.rs +++ b/kubi/src/world/chunk.rs @@ -1,5 +1,8 @@ +use std::sync::Arc; use glam::IVec3; +use atomic::Atomic; use glium::{VertexBuffer, IndexBuffer}; +use kubi_shared::worldgen::AbortState; use crate::rendering::world::ChunkVertex; pub use kubi_shared::chunk::{CHUNK_SIZE, BlockData}; @@ -39,7 +42,7 @@ pub enum DesiredChunkState { Nothing, Loaded, Rendered, - ToUnload, + Unloaded, } impl DesiredChunkState { pub fn matches_current(self, current: CurrentChunkState) -> bool { @@ -55,8 +58,10 @@ pub struct Chunk { pub mesh_index: Option, pub current_state: CurrentChunkState, pub desired_state: DesiredChunkState, + pub abortion: Option>>, pub mesh_dirty: bool, } + impl Chunk { pub fn new(position: IVec3) -> Self { Self { @@ -65,6 +70,7 @@ impl Chunk { mesh_index: None, current_state: Default::default(), desired_state: Default::default(), + abortion: None, mesh_dirty: false, } } diff --git a/kubi/src/world/loading.rs b/kubi/src/world/loading.rs index 1d4face..5798408 100644 --- a/kubi/src/world/loading.rs +++ b/kubi/src/world/loading.rs @@ -1,6 +1,8 @@ +use std::sync::Arc; +use atomic::{Atomic, Ordering}; use glam::{IVec3, ivec3}; use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType}; -use kubi_shared::networking::messages::ClientToServerMessage; +use kubi_shared::{networking::messages::ClientToServerMessage, worldgen::AbortState}; use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync, track}; use uflow::SendMode; use crate::{ @@ -15,10 +17,10 @@ use super::{ ChunkStorage, ChunkMeshStorage, chunk::{Chunk, DesiredChunkState, CHUNK_SIZE, ChunkMesh, CurrentChunkState, ChunkData}, tasks::{ChunkTaskManager, ChunkTaskResponse, ChunkTask}, - queue::BlockUpdateQueue + queue::BlockUpdateQueue, }; -const MAX_CHUNK_OPS_INGAME: usize = 6; +const MAX_CHUNK_OPS_INGAME: usize = 8; const MAX_CHUNK_OPS: usize = 32; pub fn update_loaded_world_around_player() -> Workload { @@ -56,7 +58,7 @@ pub fn update_chunks_if_player_moved( //Then, mark *ALL* chunks with ToUnload for (_, chunk) in &mut vm_world.chunks { - chunk.desired_state = DesiredChunkState::ToUnload; + chunk.desired_state = DesiredChunkState::Unloaded; } //Then mark chunks that are near to the player @@ -99,13 +101,27 @@ fn unload_downgrade_chunks( //TODO refactor this //TODO unsubscibe if in multiplayer vm_world.chunks.retain(|_, chunk| { - if chunk.desired_state == DesiredChunkState::ToUnload { + if chunk.desired_state == DesiredChunkState::Unloaded { if let Some(mesh_index) = chunk.mesh_index { vm_meshes.remove(mesh_index).unwrap(); } + if let Some(abortion) = &chunk.abortion { + let _ = abortion.compare_exchange( + AbortState::Continue, AbortState::Abort, + Ordering::Relaxed, Ordering::Relaxed + ); + } false } else { match chunk.desired_state { + DesiredChunkState::Nothing if matches!(chunk.current_state, CurrentChunkState::Loading) => { + if let Some(abortion) = &chunk.abortion { + let _ = abortion.compare_exchange( + AbortState::Continue, AbortState::Abort, + Ordering::Relaxed, Ordering::Relaxed + ); + } + }, DesiredChunkState::Loaded if matches!(chunk.current_state, CurrentChunkState::Rendered | CurrentChunkState::CalculatingMesh | CurrentChunkState::RecalculatingMesh) => { if let Some(mesh_index) = chunk.mesh_index { vm_meshes.remove(mesh_index).unwrap(); @@ -134,6 +150,7 @@ fn start_required_tasks( let chunk = world.chunks.get(&position).unwrap(); match chunk.desired_state { DesiredChunkState::Loaded | DesiredChunkState::Rendered if chunk.current_state == CurrentChunkState::Nothing => { + let mut abortion = None; //start load task if let Some(client) = &mut udp_client { client.0.send( @@ -144,14 +161,18 @@ fn start_required_tasks( SendMode::Reliable ); } else { + let atomic = Arc::new(Atomic::new(AbortState::Continue)); task_manager.spawn_task(ChunkTask::LoadChunk { seed: 0xbeef_face_dead_cafe, - position + position, + abortion: Some(Arc::clone(&atomic)), }); + abortion = Some(atomic); } //Update chunk state let chunk = world.chunks.get_mut(&position).unwrap(); chunk.current_state = CurrentChunkState::Loading; + chunk.abortion = abortion; // =========== //log::trace!("Started loading chunk {position}"); }, @@ -173,6 +194,7 @@ fn start_required_tasks( chunk.current_state = CurrentChunkState::CalculatingMesh; } chunk.mesh_dirty = false; + chunk.abortion = None; //Can never abort at this point // =========== //log::trace!("Started generating mesh for chunk {position}"); } @@ -195,14 +217,15 @@ fn process_completed_tasks( ChunkTaskResponse::LoadedChunk { position, chunk_data, mut queued } => { //check if chunk exists let Some(chunk) = world.chunks.get_mut(&position) else { + //to compensate, actually push the ops counter back by one log::warn!("blocks data discarded: chunk doesn't exist"); - return + continue }; //check if chunk still wants it if !matches!(chunk.desired_state, DesiredChunkState::Loaded | DesiredChunkState::Rendered) { log::warn!("block data discarded: state undesirable: {:?}", chunk.desired_state); - return + continue } //set the block data @@ -228,13 +251,13 @@ fn process_completed_tasks( //check if chunk exists let Some(chunk) = world.chunks.get_mut(&position) else { log::warn!("mesh discarded: chunk doesn't exist"); - return + continue }; //check if chunk still wants it if chunk.desired_state != DesiredChunkState::Rendered { log::warn!("mesh discarded: state undesirable: {:?}", chunk.desired_state); - return + continue } //apply the mesh diff --git a/kubi/src/world/tasks.rs b/kubi/src/world/tasks.rs index 6c22cdf..87bda90 100644 --- a/kubi/src/world/tasks.rs +++ b/kubi/src/world/tasks.rs @@ -1,6 +1,8 @@ +use std::sync::Arc; +use atomic::Atomic; use flume::{Sender, Receiver}; use glam::IVec3; -use kubi_shared::queue::QueuedBlock; +use kubi_shared::{queue::QueuedBlock, worldgen::AbortState}; use shipyard::Unique; use rayon::{ThreadPool, ThreadPoolBuilder}; use super::{ @@ -13,7 +15,8 @@ use crate::rendering::world::ChunkVertex; pub enum ChunkTask { LoadChunk { seed: u64, - position: IVec3 + position: IVec3, + abortion: Option>>, }, GenerateMesh { position: IVec3, @@ -67,8 +70,11 @@ impl ChunkTaskManager { trans_vertices, trans_indices, } }, - ChunkTask::LoadChunk { position, seed } => { - let (chunk_data, queued) = generate_world(position, seed); + ChunkTask::LoadChunk { position, seed, abortion } => { + let Some((chunk_data, queued)) = generate_world(position, seed, abortion) else { + log::warn!("aborted operation"); + return + }; ChunkTaskResponse::LoadedChunk { position, chunk_data, queued } } }); From b6c314e8f389fb0ce7ecef03a13d44caab6d45bc Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Thu, 2 May 2024 16:51:03 +0200 Subject: [PATCH 3/8] disable borked cave generation --- kubi-shared/src/worldgen/_03_caves.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubi-shared/src/worldgen/_03_caves.rs b/kubi-shared/src/worldgen/_03_caves.rs index 5e6d73e..f77d11a 100644 --- a/kubi-shared/src/worldgen/_03_caves.rs +++ b/kubi-shared/src/worldgen/_03_caves.rs @@ -34,7 +34,7 @@ impl WorldGenStep for CaveStep { if noise_min > 0.5 { return } - gen.place(ivec3(x, y, z), Block::Air); + //gen.place(ivec3(x, y, z), Block::Air); } } } From 56fe70765f82451ea49a9c70c3599392be09ae42 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Thu, 2 May 2024 18:15:18 +0200 Subject: [PATCH 4/8] switch libs --- Cargo.lock | 44 +++++-------------- kubi-shared/Cargo.toml | 2 +- kubi-shared/src/worldgen.rs | 25 +++++++++++ kubi-shared/src/worldgen/_01_terrain.rs | 17 ++++---- kubi-shared/src/worldgen/_02_water.rs | 1 - kubi-shared/src/worldgen/_03_caves.rs | 56 +++++++++++++------------ 6 files changed, 75 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90612c1..ef609d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -236,30 +236,6 @@ dependencies = [ "objc2", ] -[[package]] -name = "bracket-noise" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b7443d0990c69db7a83f376f0101d684c20a911698e5f932bffbda2c8b08dd" -dependencies = [ - "bracket-random", -] - -[[package]] -name = "bracket-random" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437be61484077b1ddb57002ce3c96b7d03cbf500b5d15157ee7e67e22332c39b" -dependencies = [ - "getrandom", - "js-sys", - "lazy_static", - "rand", - "rand_xorshift", - "regex", - "wasm-bindgen", -] - [[package]] name = "bumpalo" version = "3.16.0" @@ -633,6 +609,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fastnoise-lite" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5f3c3cc9081e5d0e18bcd50e80cd33cba47fc22f88a9da9c33ecd1c87ea5c0" +dependencies = [ + "num-traits", +] + [[package]] name = "fdeflate" version = "0.3.4" @@ -1243,8 +1228,8 @@ dependencies = [ "anyhow", "atomic", "bincode", - "bracket-noise", "bytemuck", + "fastnoise-lite", "glam", "hashbrown 0.14.3", "nohash-hasher", @@ -1688,15 +1673,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - [[package]] name = "rand_xoshiro" version = "0.6.0" diff --git a/kubi-shared/Cargo.toml b/kubi-shared/Cargo.toml index 63cf255..444bc73 100644 --- a/kubi-shared/Cargo.toml +++ b/kubi-shared/Cargo.toml @@ -14,7 +14,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv serde_with = "3.4" bincode = "1.3" anyhow = "1.0" -bracket-noise = "0.8" +fastnoise-lite = { version = "1.1", features = ["std", "f64"] } rand = { version = "0.8", default_features = false, features = ["std", "min_const_gen"] } rand_xoshiro = "0.6" hashbrown = { version = "0.14", features = ["serde"] } diff --git a/kubi-shared/src/worldgen.rs b/kubi-shared/src/worldgen.rs index 772aeb1..edfe938 100644 --- a/kubi-shared/src/worldgen.rs +++ b/kubi-shared/src/worldgen.rs @@ -23,6 +23,31 @@ pub enum AbortState { } const_assert!(Atomic::::is_lock_free()); +pub struct SeedThingy { + pseed: u64, + iseed: i32, + iter: u8, +} + +impl SeedThingy { + pub fn new(seed: u64) -> Self { + Self { + pseed: seed, + iseed: (seed & 0x7fffffffu64) as i32, + iter: 0, + } + } + + pub fn next_seed(&mut self) -> i32 { + self.iter += 1; + self.iseed = ( + self.pseed + .rotate_left((3 * self.iter) as _) + & 0x7fffffff + ) as i32; + self.iseed + } +} trait WorldGenStep { fn initialize(generator: &WorldGenerator) -> Self; fn generate(&mut self, generator: &mut WorldGenerator); diff --git a/kubi-shared/src/worldgen/_01_terrain.rs b/kubi-shared/src/worldgen/_01_terrain.rs index 9e21aa6..f5342a7 100644 --- a/kubi-shared/src/worldgen/_01_terrain.rs +++ b/kubi-shared/src/worldgen/_01_terrain.rs @@ -1,18 +1,19 @@ -use bracket_noise::prelude::{FastNoise, FractalType}; +use fastnoise_lite::{FastNoiseLite, FractalType}; use glam::ivec3; use crate::{block::Block, chunk::CHUNK_SIZE}; -use super::{WorldGenerator, WorldGenStep}; +use super::{SeedThingy, WorldGenStep, WorldGenerator}; pub struct TerrainStep { - noise: FastNoise, + noise: FastNoiseLite, } impl WorldGenStep for TerrainStep { fn initialize(generator: &WorldGenerator) -> Self { - let mut noise = FastNoise::seeded(generator.seed); - noise.set_fractal_type(FractalType::RigidMulti); - noise.set_fractal_octaves(5); - noise.set_frequency(0.003); + let mut seeder = SeedThingy::new(generator.seed); + let mut noise = FastNoiseLite::with_seed(seeder.next_seed()); + noise.set_fractal_type(Some(FractalType::FBm)); + noise.set_fractal_octaves(Some(4)); + noise.set_frequency(Some(0.003)); Self { noise } } @@ -20,7 +21,7 @@ impl WorldGenStep for TerrainStep { for x in 0..CHUNK_SIZE as i32 { for z in 0..CHUNK_SIZE as i32 { let global_xz = gen.global_position(ivec3(x, 0, z)); - let height = (self.noise.get_noise(global_xz.x as f32, global_xz.z as f32) * 32.0) as i32; + let height = (self.noise.get_noise_2d(global_xz.x as f64, global_xz.z as f64) * 32.0) as i32; for y in 0..gen.local_height(height) { gen.place(ivec3(x, y, z), Block::Stone); } diff --git a/kubi-shared/src/worldgen/_02_water.rs b/kubi-shared/src/worldgen/_02_water.rs index 136bc41..63c0b11 100644 --- a/kubi-shared/src/worldgen/_02_water.rs +++ b/kubi-shared/src/worldgen/_02_water.rs @@ -1,4 +1,3 @@ -use bracket_noise::prelude::{FastNoise, FractalType}; use glam::ivec3; use crate::{block::Block, chunk::CHUNK_SIZE}; use super::{WorldGenerator, WorldGenStep}; diff --git a/kubi-shared/src/worldgen/_03_caves.rs b/kubi-shared/src/worldgen/_03_caves.rs index f77d11a..a0ddd5a 100644 --- a/kubi-shared/src/worldgen/_03_caves.rs +++ b/kubi-shared/src/worldgen/_03_caves.rs @@ -1,42 +1,46 @@ -use bracket_noise::prelude::{FastNoise, FractalType}; + +use fastnoise_lite::{FastNoiseLite, FractalType}; use glam::{ivec3, IVec3}; use crate::{block::Block, chunk::CHUNK_SIZE}; -use super::{WorldGenStep, WorldGenerator}; +use super::{SeedThingy, WorldGenStep, WorldGenerator}; pub struct CaveStep { - a: FastNoise, - b: FastNoise, + a: FastNoiseLite, + b: FastNoiseLite, } impl WorldGenStep for CaveStep { fn initialize(gen: &WorldGenerator) -> Self { - let mut a = FastNoise::seeded(gen.seed); - a.set_fractal_type(FractalType::FBM); - a.set_frequency(0.015); + let mut seeder = SeedThingy::new(gen.seed); - let mut b = FastNoise::seeded(gen.seed.rotate_left(1) + 1); - b.set_fractal_type(FractalType::FBM); - b.set_frequency(0.015); + let mut a = FastNoiseLite::with_seed(seeder.next_seed()); + a.set_fractal_type(Some(FractalType::FBm)); + a.set_fractal_octaves(Some(2)); + + let mut b = FastNoiseLite::with_seed(seeder.next_seed()); + b.set_fractal_type(Some(FractalType::FBm)); + b.set_fractal_octaves(Some(2)); Self { a, b } } + fn generate(&mut self, gen: &mut WorldGenerator) { - for x in 0..CHUNK_SIZE as i32 { - for z in 0..CHUNK_SIZE as i32 { - for y in 0..CHUNK_SIZE as i32 { - let pos: IVec3 = ivec3(x, y, z); - if gen.query(pos) != Block::Stone { continue } + //TODO - let gpos = gen.global_position(pos); - let noise_a = self.a.get_noise3d(gpos.x as f32, gpos.y as f32, gpos.z as f32); - let noise_b = self.b.get_noise3d(gpos.x as f32, gpos.y as f32, gpos.z as f32); - let noise_min = noise_a.min(noise_b); - - if noise_min > 0.5 { return } - - //gen.place(ivec3(x, y, z), Block::Air); - } - } - } + // for x in 0..CHUNK_SIZE as i32 { + // for y in 0..CHUNK_SIZE as i32 { + // for z in 0..CHUNK_SIZE as i32 { + // let pos = ivec3(x, y, z); + // if gen.query(pos) != Block::Stone { continue } + // let pos_global = gen.global_position(pos); + // let noise_a = self.a.get_noise_3d(pos_global.x as f64, pos_global.y as f64, pos_global.z as f64) * 0.5 + 0.5; + // let noise_b = self.b.get_noise_3d(pos_global.x as f64, pos_global.y as f64, pos_global.z as f64) * 0.5 + 0.5; + // if noise_a.min(noise_b) > (1. - (-y as f32 / 400.).clamp(0., 1.)) { + // gen.place(pos, Block::Air); + // } + // //TODO + // } + // } + // } } } From b487da647477d22f8159886a3d6608add8ff0204 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Thu, 2 May 2024 18:16:29 +0200 Subject: [PATCH 5/8] check before first step --- kubi-shared/src/worldgen.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kubi-shared/src/worldgen.rs b/kubi-shared/src/worldgen.rs index edfe938..d15d8bd 100644 --- a/kubi-shared/src/worldgen.rs +++ b/kubi-shared/src/worldgen.rs @@ -66,6 +66,8 @@ macro_rules! run_steps { ::atomic::Ordering::Relaxed ).is_ok(); + if _chkabt() { return false } + $({ let _ensure_ref: &mut $crate::worldgen::WorldGenerator = $gen; struct _Ensure0(T); From b1de67339e316e8a384130da03f04bed4b22f523 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Thu, 2 May 2024 22:27:49 +0200 Subject: [PATCH 6/8] add opt lvl --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index d41d6d9..ae628a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,9 @@ opt-level = 3 [profile.dev.package.bracket-noise] opt-level = 3 +[profile.dev.package.fastnoise-lite] +opt-level = 3 + [profile.dev.package.rayon] opt-level = 3 From 0fac4531e049bf6c93ae74d489fc46acd691b50d Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Fri, 3 May 2024 00:27:43 +0200 Subject: [PATCH 7/8] decoration and layers --- kubi-shared/src/worldgen.rs | 21 +++++++++++++ kubi-shared/src/worldgen/_01_terrain.rs | 3 ++ kubi-shared/src/worldgen/_02_water.rs | 4 ++- kubi-shared/src/worldgen/_03_caves.rs | 39 +++++++++++++----------- kubi-shared/src/worldgen/_04_layers.rs | 32 +++++++++++++++++++ kubi-shared/src/worldgen/_05_decorate.rs | 28 +++++++++++++++++ 6 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 kubi-shared/src/worldgen/_04_layers.rs create mode 100644 kubi-shared/src/worldgen/_05_decorate.rs diff --git a/kubi-shared/src/worldgen.rs b/kubi-shared/src/worldgen.rs index d15d8bd..d8602b8 100644 --- a/kubi-shared/src/worldgen.rs +++ b/kubi-shared/src/worldgen.rs @@ -12,6 +12,8 @@ use crate::{ mod _01_terrain; mod _02_water; mod _03_caves; +mod _04_layers; +mod _05_decorate; #[repr(u8)] #[derive(Clone, Copy, Debug, Default, NoUninit, CheckedBitPattern)] @@ -83,11 +85,17 @@ macro_rules! run_steps { }; } +#[derive(Default)] +pub struct WorldGeneratorData { + pub master_height_map: Option>>, +} + pub struct WorldGenerator { seed: u64, chunk_position: IVec3, blocks: BlockData, queue: Vec, + pub data: WorldGeneratorData, } impl WorldGenerator { @@ -153,12 +161,23 @@ impl WorldGenerator { (0..CHUNK_SIZE as i32).contains(&position).then_some(position) } + /// crude hash of self.seed and x + fn seeded_hash(&self, x: impl std::hash::Hash) -> u64 { + //use std::hash to hash the seed and x + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + x.hash(&mut hasher); + self.seed.hash(&mut hasher); + hasher.finish() + } + pub fn new(chunk_position: IVec3, seed: u64) -> Self { Self { seed, chunk_position, blocks: Box::new([[[Block::Air; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]), queue: Vec::with_capacity(0), + data: Default::default(), } } @@ -170,6 +189,8 @@ impl WorldGenerator { _01_terrain::TerrainStep, _02_water::WaterStep, _03_caves::CaveStep, + _04_layers::LayersStep, + _05_decorate::DecorateStep, ]).then_some((self.blocks, self.queue)) } } diff --git a/kubi-shared/src/worldgen/_01_terrain.rs b/kubi-shared/src/worldgen/_01_terrain.rs index f5342a7..dc1da1d 100644 --- a/kubi-shared/src/worldgen/_01_terrain.rs +++ b/kubi-shared/src/worldgen/_01_terrain.rs @@ -18,14 +18,17 @@ impl WorldGenStep for TerrainStep { } fn generate(&mut self, gen: &mut WorldGenerator) { + let mut height_map = vec![vec![0; CHUNK_SIZE]; CHUNK_SIZE]; for x in 0..CHUNK_SIZE as i32 { for z in 0..CHUNK_SIZE as i32 { let global_xz = gen.global_position(ivec3(x, 0, z)); let height = (self.noise.get_noise_2d(global_xz.x as f64, global_xz.z as f64) * 32.0) as i32; + height_map[x as usize][z as usize] = height; for y in 0..gen.local_height(height) { gen.place(ivec3(x, y, z), Block::Stone); } } } + gen.data.master_height_map = Some(height_map); } } diff --git a/kubi-shared/src/worldgen/_02_water.rs b/kubi-shared/src/worldgen/_02_water.rs index 63c0b11..f761f5e 100644 --- a/kubi-shared/src/worldgen/_02_water.rs +++ b/kubi-shared/src/worldgen/_02_water.rs @@ -2,6 +2,8 @@ use glam::ivec3; use crate::{block::Block, chunk::CHUNK_SIZE}; use super::{WorldGenerator, WorldGenStep}; +pub const WATER_LEVEL: i32 = 0; + pub struct WaterStep; impl WorldGenStep for WaterStep { @@ -9,7 +11,7 @@ impl WorldGenStep for WaterStep { fn generate(&mut self, gen: &mut WorldGenerator) { for x in 0..CHUNK_SIZE as i32 { for z in 0..CHUNK_SIZE as i32 { - for y in 0..gen.local_height(0) { + for y in 0..gen.local_height(WATER_LEVEL) { gen.place_if_empty(ivec3(x, y, z), Block::Water); } } diff --git a/kubi-shared/src/worldgen/_03_caves.rs b/kubi-shared/src/worldgen/_03_caves.rs index a0ddd5a..bfb0a75 100644 --- a/kubi-shared/src/worldgen/_03_caves.rs +++ b/kubi-shared/src/worldgen/_03_caves.rs @@ -1,6 +1,5 @@ - use fastnoise_lite::{FastNoiseLite, FractalType}; -use glam::{ivec3, IVec3}; +use glam::{ivec3, FloatExt, IVec3}; use crate::{block::Block, chunk::CHUNK_SIZE}; use super::{SeedThingy, WorldGenStep, WorldGenerator}; @@ -25,22 +24,26 @@ impl WorldGenStep for CaveStep { } fn generate(&mut self, gen: &mut WorldGenerator) { - //TODO + for x in 0..CHUNK_SIZE as i32 { + for y in 0..CHUNK_SIZE as i32 { + for z in 0..CHUNK_SIZE as i32 { + let cave_size = ((gen.offset().y + y - 50) as f64 / -200.).clamp(0., 1.) as f32; + let inv_cave_size = 1. - cave_size; + if cave_size < 0.1 { continue } - // for x in 0..CHUNK_SIZE as i32 { - // for y in 0..CHUNK_SIZE as i32 { - // for z in 0..CHUNK_SIZE as i32 { - // let pos = ivec3(x, y, z); - // if gen.query(pos) != Block::Stone { continue } - // let pos_global = gen.global_position(pos); - // let noise_a = self.a.get_noise_3d(pos_global.x as f64, pos_global.y as f64, pos_global.z as f64) * 0.5 + 0.5; - // let noise_b = self.b.get_noise_3d(pos_global.x as f64, pos_global.y as f64, pos_global.z as f64) * 0.5 + 0.5; - // if noise_a.min(noise_b) > (1. - (-y as f32 / 400.).clamp(0., 1.)) { - // gen.place(pos, Block::Air); - // } - // //TODO - // } - // } - // } + let pos = ivec3(x, y, z); + if gen.query(pos) != Block::Stone { continue } + + let pos_global = gen.global_position(pos); + let noise_a = self.a.get_noise_3d(pos_global.x as f64, pos_global.y as f64, pos_global.z as f64) * 0.5 + 0.5; + let noise_b = self.b.get_noise_3d(pos_global.x as f64, pos_global.y as f64, pos_global.z as f64) * 0.5 + 0.5; + + if noise_a.min(noise_b) > (0.6 + 0.4 * inv_cave_size) { + gen.place(pos, Block::Air); + } + //TODO + } + } + } } } diff --git a/kubi-shared/src/worldgen/_04_layers.rs b/kubi-shared/src/worldgen/_04_layers.rs new file mode 100644 index 0000000..d9a2dc8 --- /dev/null +++ b/kubi-shared/src/worldgen/_04_layers.rs @@ -0,0 +1,32 @@ +use glam::ivec3; +use crate::{block::Block, chunk::CHUNK_SIZE}; +use super::{WorldGenStep, WorldGenerator, _02_water::WATER_LEVEL}; +pub struct LayersStep; + +impl WorldGenStep for LayersStep { + fn initialize(_: &WorldGenerator) -> Self { Self } + + fn generate(&mut self, gen: &mut WorldGenerator) { + for x in 0..CHUNK_SIZE as i32 { + for z in 0..CHUNK_SIZE as i32 { + let terrain_height = gen.data.master_height_map.as_ref().unwrap()[x as usize][z as usize]; + + // Dirt layer height, naturally gets thinner as height gets deeper + let mut dirt_layer_height = (((terrain_height as f32 + 15.) / 20.).clamp(0., 1.) * 8.).round() as i32; + dirt_layer_height -= (gen.seeded_hash((x, z, 1)) & 1) as i32; //+ (gen.seeded_hash((x, z, 0xbau8)) & 1) as i32; + + // Place dirt layer + for y in gen.local_height(terrain_height - dirt_layer_height)..gen.local_height(terrain_height) { + gen.place(ivec3(x, y, z), Block::Dirt); + } + + // If above water level, place grass + if terrain_height >= WATER_LEVEL { + if let Some(local_y) = gen.local_y_position(terrain_height - 1) { + gen.place(ivec3(x, local_y, z), Block::Grass); + } + } + } + } + } +} diff --git a/kubi-shared/src/worldgen/_05_decorate.rs b/kubi-shared/src/worldgen/_05_decorate.rs new file mode 100644 index 0000000..a4850de --- /dev/null +++ b/kubi-shared/src/worldgen/_05_decorate.rs @@ -0,0 +1,28 @@ +use glam::ivec3; +use crate::{block::Block, chunk::CHUNK_SIZE}; +use super::{WorldGenStep, WorldGenerator, _02_water::WATER_LEVEL}; + +pub struct DecorateStep; + +impl WorldGenStep for DecorateStep { + fn initialize(_: &WorldGenerator) -> Self { Self } + + fn generate(&mut self, gen: &mut WorldGenerator) { + for x in 0..CHUNK_SIZE as i32 { + for z in 0..CHUNK_SIZE as i32 { + let global_xz = gen.global_position(ivec3(x, 0, z)); + + let terrain_height = gen.data.master_height_map.as_ref().unwrap()[x as usize][z as usize]; + + //Place tall grass + if terrain_height >= WATER_LEVEL { + if let Some(local_y) = gen.local_y_position(terrain_height) { + if (gen.seeded_hash((global_xz.x, global_xz.z)) & 0xf) == 0xf { + gen.place_if_empty(ivec3(x, local_y, z), Block::TallGrass); + } + } + } + } + } + } +} From cfffb403a348723a55e02f2d555d5848ce8b0a62 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Fri, 3 May 2024 01:21:56 +0200 Subject: [PATCH 8/8] move stuff, forests! --- kubi-shared/src/worldgen.rs | 18 +++--- kubi-shared/src/worldgen/steps.rs | 6 ++ .../src/worldgen/{ => steps}/_01_terrain.rs | 2 +- .../src/worldgen/{ => steps}/_02_water.rs | 2 +- .../src/worldgen/{ => steps}/_03_caves.rs | 2 +- .../src/worldgen/{ => steps}/_04_layers.rs | 6 +- .../src/worldgen/{ => steps}/_05_decorate.rs | 5 +- kubi-shared/src/worldgen/steps/_06_trees.rs | 45 ++++++++++++++ kubi-shared/src/worldgen/structures.rs | 9 +++ kubi-shared/src/worldgen/structures/tree.rs | 58 +++++++++++++++++++ 10 files changed, 138 insertions(+), 15 deletions(-) create mode 100644 kubi-shared/src/worldgen/steps.rs rename kubi-shared/src/worldgen/{ => steps}/_01_terrain.rs (92%) rename kubi-shared/src/worldgen/{ => steps}/_02_water.rs (87%) rename kubi-shared/src/worldgen/{ => steps}/_03_caves.rs (93%) rename kubi-shared/src/worldgen/{ => steps}/_04_layers.rs (91%) rename kubi-shared/src/worldgen/{ => steps}/_05_decorate.rs (88%) create mode 100644 kubi-shared/src/worldgen/steps/_06_trees.rs create mode 100644 kubi-shared/src/worldgen/structures.rs create mode 100644 kubi-shared/src/worldgen/structures/tree.rs diff --git a/kubi-shared/src/worldgen.rs b/kubi-shared/src/worldgen.rs index d8602b8..f5ebba6 100644 --- a/kubi-shared/src/worldgen.rs +++ b/kubi-shared/src/worldgen.rs @@ -9,11 +9,8 @@ use crate::{ queue::QueuedBlock, }; -mod _01_terrain; -mod _02_water; -mod _03_caves; -mod _04_layers; -mod _05_decorate; +pub mod steps; +pub mod structures; #[repr(u8)] #[derive(Clone, Copy, Debug, Default, NoUninit, CheckedBitPattern)] @@ -186,11 +183,12 @@ impl WorldGenerator { /// Will return `None` only if the generation was aborted. pub fn generate(mut self, abort: Option>>) -> Option<(BlockData, Vec)> { run_steps!(&mut self, abort, [ - _01_terrain::TerrainStep, - _02_water::WaterStep, - _03_caves::CaveStep, - _04_layers::LayersStep, - _05_decorate::DecorateStep, + steps::_01_terrain::TerrainStep, + steps::_02_water::WaterStep, + steps::_03_caves::CaveStep, + steps::_04_layers::LayersStep, + steps::_05_decorate::DecorateStep, + steps::_06_trees::TreesStep, ]).then_some((self.blocks, self.queue)) } } diff --git a/kubi-shared/src/worldgen/steps.rs b/kubi-shared/src/worldgen/steps.rs new file mode 100644 index 0000000..147f85d --- /dev/null +++ b/kubi-shared/src/worldgen/steps.rs @@ -0,0 +1,6 @@ +pub mod _01_terrain; +pub mod _02_water; +pub mod _03_caves; +pub mod _04_layers; +pub mod _05_decorate; +pub mod _06_trees; diff --git a/kubi-shared/src/worldgen/_01_terrain.rs b/kubi-shared/src/worldgen/steps/_01_terrain.rs similarity index 92% rename from kubi-shared/src/worldgen/_01_terrain.rs rename to kubi-shared/src/worldgen/steps/_01_terrain.rs index dc1da1d..0a0bc36 100644 --- a/kubi-shared/src/worldgen/_01_terrain.rs +++ b/kubi-shared/src/worldgen/steps/_01_terrain.rs @@ -1,7 +1,7 @@ use fastnoise_lite::{FastNoiseLite, FractalType}; use glam::ivec3; use crate::{block::Block, chunk::CHUNK_SIZE}; -use super::{SeedThingy, WorldGenStep, WorldGenerator}; +use super::super::{SeedThingy, WorldGenStep, WorldGenerator}; pub struct TerrainStep { noise: FastNoiseLite, diff --git a/kubi-shared/src/worldgen/_02_water.rs b/kubi-shared/src/worldgen/steps/_02_water.rs similarity index 87% rename from kubi-shared/src/worldgen/_02_water.rs rename to kubi-shared/src/worldgen/steps/_02_water.rs index f761f5e..80c6a3a 100644 --- a/kubi-shared/src/worldgen/_02_water.rs +++ b/kubi-shared/src/worldgen/steps/_02_water.rs @@ -1,6 +1,6 @@ use glam::ivec3; use crate::{block::Block, chunk::CHUNK_SIZE}; -use super::{WorldGenerator, WorldGenStep}; +use super::super::{WorldGenerator, WorldGenStep}; pub const WATER_LEVEL: i32 = 0; diff --git a/kubi-shared/src/worldgen/_03_caves.rs b/kubi-shared/src/worldgen/steps/_03_caves.rs similarity index 93% rename from kubi-shared/src/worldgen/_03_caves.rs rename to kubi-shared/src/worldgen/steps/_03_caves.rs index bfb0a75..41d0be2 100644 --- a/kubi-shared/src/worldgen/_03_caves.rs +++ b/kubi-shared/src/worldgen/steps/_03_caves.rs @@ -1,7 +1,7 @@ use fastnoise_lite::{FastNoiseLite, FractalType}; use glam::{ivec3, FloatExt, IVec3}; use crate::{block::Block, chunk::CHUNK_SIZE}; -use super::{SeedThingy, WorldGenStep, WorldGenerator}; +use super::super::{SeedThingy, WorldGenStep, WorldGenerator}; pub struct CaveStep { a: FastNoiseLite, diff --git a/kubi-shared/src/worldgen/_04_layers.rs b/kubi-shared/src/worldgen/steps/_04_layers.rs similarity index 91% rename from kubi-shared/src/worldgen/_04_layers.rs rename to kubi-shared/src/worldgen/steps/_04_layers.rs index d9a2dc8..a80354f 100644 --- a/kubi-shared/src/worldgen/_04_layers.rs +++ b/kubi-shared/src/worldgen/steps/_04_layers.rs @@ -1,6 +1,10 @@ use glam::ivec3; use crate::{block::Block, chunk::CHUNK_SIZE}; -use super::{WorldGenStep, WorldGenerator, _02_water::WATER_LEVEL}; +use super::{ + _02_water::WATER_LEVEL, + super::{WorldGenStep, WorldGenerator} +}; + pub struct LayersStep; impl WorldGenStep for LayersStep { diff --git a/kubi-shared/src/worldgen/_05_decorate.rs b/kubi-shared/src/worldgen/steps/_05_decorate.rs similarity index 88% rename from kubi-shared/src/worldgen/_05_decorate.rs rename to kubi-shared/src/worldgen/steps/_05_decorate.rs index a4850de..20e9b32 100644 --- a/kubi-shared/src/worldgen/_05_decorate.rs +++ b/kubi-shared/src/worldgen/steps/_05_decorate.rs @@ -1,6 +1,9 @@ use glam::ivec3; use crate::{block::Block, chunk::CHUNK_SIZE}; -use super::{WorldGenStep, WorldGenerator, _02_water::WATER_LEVEL}; +use super::{ + _02_water::WATER_LEVEL, + super::{WorldGenStep, WorldGenerator}, +}; pub struct DecorateStep; diff --git a/kubi-shared/src/worldgen/steps/_06_trees.rs b/kubi-shared/src/worldgen/steps/_06_trees.rs new file mode 100644 index 0000000..08b547b --- /dev/null +++ b/kubi-shared/src/worldgen/steps/_06_trees.rs @@ -0,0 +1,45 @@ +use bincode::de; +use fastnoise_lite::{FastNoiseLite, NoiseType}; +use glam::ivec3; +use crate::{block::Block, chunk::CHUNK_SIZE, worldgen::SeedThingy}; +use super::_02_water::WATER_LEVEL; +use crate::worldgen::{ + WorldGenStep, WorldGenerator, + structures::{Structure, TreeStructure}, +}; + + +pub struct TreesStep { + density_noise: FastNoiseLite, +} + +impl WorldGenStep for TreesStep { + fn initialize(gen: &WorldGenerator) -> Self { + let mut seeder = SeedThingy::new(gen.seed.rotate_left(5)); + let mut density_noise = FastNoiseLite::with_seed(seeder.next_seed()); + density_noise.set_noise_type(Some(NoiseType::OpenSimplex2)); + density_noise.set_frequency(Some(0.008)); + Self { density_noise } + } + + fn generate(&mut self, gen: &mut WorldGenerator) { + for x in 0..CHUNK_SIZE as i32 { + for z in 0..CHUNK_SIZE as i32 { + let terrain_height = gen.data.master_height_map.as_ref().unwrap()[x as usize][z as usize]; + if terrain_height < WATER_LEVEL { continue } + + let global_xz = gen.global_position(ivec3(x, 0, z)); + let mut density = self.density_noise.get_noise_2d(global_xz.x as f64, global_xz.z as f64) * 0.5 + 0.5; + density = density.powi(3); + if gen.seeded_hash((global_xz.x, global_xz.z, 0xfef)) & 0xff >= (density * 7.).round() as u64 { + continue + } + + let tree = TreeStructure::default(); + if let Some(local_y) = gen.local_y_position(terrain_height) { + tree.place(gen, ivec3(x, local_y, z)); + } + } + } + } +} diff --git a/kubi-shared/src/worldgen/structures.rs b/kubi-shared/src/worldgen/structures.rs new file mode 100644 index 0000000..744cab6 --- /dev/null +++ b/kubi-shared/src/worldgen/structures.rs @@ -0,0 +1,9 @@ +use glam::IVec3; +use super::WorldGenerator; + +mod tree; +pub use tree::TreeStructure; + +pub trait Structure { + fn place(&self, gen: &mut WorldGenerator, root_pos: IVec3); +} diff --git a/kubi-shared/src/worldgen/structures/tree.rs b/kubi-shared/src/worldgen/structures/tree.rs new file mode 100644 index 0000000..78083b3 --- /dev/null +++ b/kubi-shared/src/worldgen/structures/tree.rs @@ -0,0 +1,58 @@ +use glam::IVec3; +use super::Structure; +use crate::{block::Block, worldgen::WorldGenerator}; + +#[derive(Clone, Copy, Debug)] +pub struct TreeStructure { + pub height: i32, +} + +impl Default for TreeStructure { + fn default() -> Self { + Self { height: 5 } + } +} + +impl Structure for TreeStructure { + fn place(&self, gen: &mut WorldGenerator, root: IVec3) { + //check the block below the tree, if it's grass, replace it with dirt + //XXX: This won't work if root.y == 0 + if root.y != 0 && gen.query(root - IVec3::Y) == Block::Grass { + gen.place(root - IVec3::Y, Block::Dirt); + } + + //Tree stem + for y in root.y..root.y + self.height { + gen.place_or_queue(IVec3::new(root.x, y, root.z), Block::Wood); + } + + //Tree leaves + //Try to create the following shape: + //(a 5x2x5 cube that wraps around the stem with a 3x1x3 cube on top) + // xxx + // xx|xx + // xx|xx + // | + + for y in 0..=4_i32 { + for x in -2..=2_i32 { + for z in -2..=2_i32 { + //Do not overwrite the stem + if y < 3 && x == 0 && z == 0 { + continue + } + // Cut off the corners of the top layer + if y >= 3 && (x.abs() > 1 || z.abs() > 1) { + continue + } + let position = IVec3::new( + root.x + x, + root.y + self.height - 3 + y, + root.z + z + ); + gen.place_or_queue(position, Block::Leaf); + } + } + } + } +}