diff --git a/Cargo.lock b/Cargo.lock index b3e8d2655..a1d3c4a2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1543,6 +1543,17 @@ dependencies = [ "libc", ] +[[package]] +name = "noise" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6da45c8333f2e152fc665d78a380be060eb84fad8ca4c9f7ac8ca29216cff0cc" +dependencies = [ + "num-traits", + "rand", + "rand_xorshift", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1977,9 +1988,11 @@ dependencies = [ "futures", "itertools 0.13.0", "log", + "noise", "num-derive", "num-traits", "pumpkin-core", + "rand", "rayon", "serde", "serde_json", @@ -2075,6 +2088,15 @@ 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 = "rayon" version = "1.10.0" diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 2067e8050..90be69d5c 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -19,5 +19,9 @@ serde_json = "1.0" static_assertions = "1.1.0" log.workspace = true +noise = "0.9.0" + +rand = "0.8.5" + num-traits = "0.2" num-derive = "0.4" diff --git a/pumpkin-world/src/world_gen/generator.rs b/pumpkin-world/src/world_gen/generator.rs index d74949e00..0a7d043d6 100644 --- a/pumpkin-world/src/world_gen/generator.rs +++ b/pumpkin-world/src/world_gen/generator.rs @@ -1,3 +1,4 @@ +use noise::Perlin; use pumpkin_core::math::vector2::Vector2; use static_assertions::assert_obj_safe; @@ -20,6 +21,17 @@ pub(crate) trait BiomeGenerator: Sync + Send { fn generate_biome(&self, at: XZBlockCoordinates) -> Biome; } +#[expect(dead_code)] pub(crate) trait TerrainGenerator: Sync + Send { + fn prepare_chunk(&self, at: &Vector2); + + /// Is static fn generate_block(&self, at: BlockCoordinates, biome: Biome) -> BlockId; } + +pub(crate) trait PerlinTerrainGenerator: Sync + Send { + fn prepare_chunk(&self, at: &Vector2, perlin: &Perlin); + + /// Dependens on the perlin noise height + fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, biome: Biome) -> BlockId; +} diff --git a/pumpkin-world/src/world_gen/generic_generator.rs b/pumpkin-world/src/world_gen/generic_generator.rs index 6cb87ce22..8bdc17135 100644 --- a/pumpkin-world/src/world_gen/generic_generator.rs +++ b/pumpkin-world/src/world_gen/generic_generator.rs @@ -1,35 +1,46 @@ +use noise::{NoiseFn, Perlin}; use pumpkin_core::math::vector2::Vector2; use crate::{ chunk::{ChunkBlocks, ChunkData}, coordinates::{ChunkRelativeBlockCoordinates, ChunkRelativeXZBlockCoordinates}, - WORLD_LOWEST_Y, WORLD_MAX_Y, + WORLD_LOWEST_Y, }; use super::{ - generator::{BiomeGenerator, GeneratorInit, TerrainGenerator, WorldGenerator}, + generator::{BiomeGenerator, GeneratorInit, PerlinTerrainGenerator, WorldGenerator}, Seed, }; -pub struct GenericGenerator { +pub struct GenericGenerator { biome_generator: B, terrain_generator: T, + // TODO: May make this optional?. But would be pain to use in most biomes then. Maybe make a new trait like + // PerlinTerrainGenerator + perlin: Perlin, } -impl GeneratorInit +impl GeneratorInit for GenericGenerator { fn new(seed: Seed) -> Self { Self { biome_generator: B::new(seed), terrain_generator: T::new(seed), + perlin: Perlin::new(seed.0 as u32), } } } -impl WorldGenerator for GenericGenerator { +impl WorldGenerator for GenericGenerator { fn generate_chunk(&self, at: Vector2) -> ChunkData { let mut blocks = ChunkBlocks::default(); + self.terrain_generator.prepare_chunk(&at, &self.perlin); + let noise_value = self.perlin.get([at.x as f64 / 16.0, at.z as f64 / 16.0]); + + let base_height = 64.0; + let height_variation = 16.0; + let chunk_height = (noise_value * height_variation + base_height) as i32; for x in 0..16u8 { for z in 0..16u8 { @@ -42,7 +53,7 @@ impl WorldGenerator for GenericGenerator ); // Iterate from the highest block to the lowest, in order to minimize the heightmap updates - for y in (WORLD_LOWEST_Y..WORLD_MAX_Y).rev() { + for y in (WORLD_LOWEST_Y..chunk_height as i16).rev() { let coordinates = ChunkRelativeBlockCoordinates { x: x.into(), y: y.into(), @@ -51,8 +62,11 @@ impl WorldGenerator for GenericGenerator blocks.set_block( coordinates, - self.terrain_generator - .generate_block(coordinates.with_chunk_coordinates(at), biome), + self.terrain_generator.generate_block( + coordinates.with_chunk_coordinates(at), + chunk_height as i16, + biome, + ), ); } } @@ -64,3 +78,42 @@ impl WorldGenerator for GenericGenerator } } } + +// TODO: implement static terrain generator +/* +fn generate_chunk(&mut self, at: Vector2) -> ChunkData { + let mut blocks = ChunkBlocks::default(); + self.terrain_generator.prepare_chunk(&at, &self.perlin); + for x in 0..16u8 { + for z in 0..16u8 { + let biome = self.biome_generator.generate_biome( + ChunkRelativeXZBlockCoordinates { + x: x.into(), + z: z.into(), + } + .with_chunk_coordinates(at), + ); + + // Iterate from the highest block to the lowest, in order to minimize the heightmap updates + for y in (WORLD_LOWEST_Y..WORLD_MAX_Y).rev() { + let coordinates = ChunkRelativeBlockCoordinates { + x: x.into(), + y: y.into(), + z: z.into(), + }; + + blocks.set_block( + coordinates, + self.terrain_generator + .generate_block(coordinates.with_chunk_coordinates(at), biome), + ); + } + } + } + + ChunkData { + blocks, + position: at, + } +} +*/ diff --git a/pumpkin-world/src/world_gen/implementations/mod.rs b/pumpkin-world/src/world_gen/implementation/mod.rs similarity index 50% rename from pumpkin-world/src/world_gen/implementations/mod.rs rename to pumpkin-world/src/world_gen/implementation/mod.rs index 716121f9d..7aa11a83b 100644 --- a/pumpkin-world/src/world_gen/implementations/mod.rs +++ b/pumpkin-world/src/world_gen/implementation/mod.rs @@ -1 +1,2 @@ +pub mod overworld; pub mod superflat; diff --git a/pumpkin-world/src/world_gen/implementation/overworld/biome/mod.rs b/pumpkin-world/src/world_gen/implementation/overworld/biome/mod.rs new file mode 100644 index 000000000..df9a5d69f --- /dev/null +++ b/pumpkin-world/src/world_gen/implementation/overworld/biome/mod.rs @@ -0,0 +1 @@ +pub mod plains; diff --git a/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs b/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs new file mode 100644 index 000000000..cb3f3e981 --- /dev/null +++ b/pumpkin-world/src/world_gen/implementation/overworld/biome/plains.rs @@ -0,0 +1,60 @@ +use noise::Perlin; +use pumpkin_core::math::vector2::Vector2; + +use crate::{ + biome::Biome, + block::BlockId, + coordinates::{BlockCoordinates, XZBlockCoordinates}, + world_gen::{ + generator::{BiomeGenerator, GeneratorInit, PerlinTerrainGenerator}, + generic_generator::GenericGenerator, + Seed, + }, +}; + +pub type PlainsGenerator = GenericGenerator; + +pub(crate) struct PlainsBiomeGenerator {} + +impl GeneratorInit for PlainsBiomeGenerator { + fn new(_: Seed) -> Self { + Self {} + } +} + +impl BiomeGenerator for PlainsBiomeGenerator { + // TODO make generic over Biome and allow changing the Biome in the config. + fn generate_biome(&self, _: XZBlockCoordinates) -> Biome { + Biome::Plains + } +} + +pub(crate) struct PlainsTerrainGenerator {} + +impl GeneratorInit for PlainsTerrainGenerator { + fn new(_: Seed) -> Self { + Self {} + } +} + +impl PerlinTerrainGenerator for PlainsTerrainGenerator { + fn prepare_chunk(&self, _at: &Vector2, _perlin: &Perlin) {} + // TODO allow specifying which blocks should be at which height in the config. + fn generate_block(&self, at: BlockCoordinates, chunk_height: i16, _: Biome) -> BlockId { + let begin_stone_height = chunk_height - 5; + let begin_dirt_height = chunk_height - 1; + + let y = *at.y; + if y == -64 { + BlockId::from_id(79) // BEDROCK + } else if y >= -63 && y <= begin_stone_height { + return BlockId::from_id(1); // STONE + } else if y >= begin_stone_height && y < begin_dirt_height { + return BlockId::from_id(10); // DIRT; + } else if y == chunk_height - 1 { + return BlockId::from_id(9); // GRASS BLOCK + } else { + BlockId::AIR + } + } +} diff --git a/pumpkin-world/src/world_gen/implementation/overworld/mod.rs b/pumpkin-world/src/world_gen/implementation/overworld/mod.rs new file mode 100644 index 000000000..f1af41c40 --- /dev/null +++ b/pumpkin-world/src/world_gen/implementation/overworld/mod.rs @@ -0,0 +1 @@ +pub mod biome; diff --git a/pumpkin-world/src/world_gen/implementations/superflat.rs b/pumpkin-world/src/world_gen/implementation/superflat.rs similarity index 92% rename from pumpkin-world/src/world_gen/implementations/superflat.rs rename to pumpkin-world/src/world_gen/implementation/superflat.rs index 9d0e57efd..3acf32495 100644 --- a/pumpkin-world/src/world_gen/implementations/superflat.rs +++ b/pumpkin-world/src/world_gen/implementation/superflat.rs @@ -1,3 +1,5 @@ +use pumpkin_core::math::vector2::Vector2; + use crate::{ biome::Biome, block::BlockId, @@ -9,6 +11,7 @@ use crate::{ }, }; +#[expect(dead_code)] pub type SuperflatGenerator = GenericGenerator; pub(crate) struct SuperflatBiomeGenerator {} @@ -35,6 +38,7 @@ impl GeneratorInit for SuperflatTerrainGenerator { } impl TerrainGenerator for SuperflatTerrainGenerator { + fn prepare_chunk(&self, _at: &Vector2) {} // TODO allow specifying which blocks should be at which height in the config. fn generate_block(&self, at: BlockCoordinates, _: Biome) -> BlockId { match *at.y { diff --git a/pumpkin-world/src/world_gen/mod.rs b/pumpkin-world/src/world_gen/mod.rs index fd0692bdd..ddb1bbb9e 100644 --- a/pumpkin-world/src/world_gen/mod.rs +++ b/pumpkin-world/src/world_gen/mod.rs @@ -1,15 +1,15 @@ mod generator; mod generic_generator; -mod implementations; +mod implementation; mod seed; pub use generator::WorldGenerator; +use implementation::overworld::biome::plains::PlainsGenerator; pub use seed::Seed; use generator::GeneratorInit; -use implementations::superflat::SuperflatGenerator; pub fn get_world_gen(seed: Seed) -> Box { // TODO decide which WorldGenerator to pick based on config. - Box::new(SuperflatGenerator::new(seed)) + Box::new(PlainsGenerator::new(seed)) } diff --git a/pumpkin-world/src/world_gen/seed.rs b/pumpkin-world/src/world_gen/seed.rs index 884eb4a3d..137a6ecdd 100644 --- a/pumpkin-world/src/world_gen/seed.rs +++ b/pumpkin-world/src/world_gen/seed.rs @@ -1,6 +1,5 @@ use std::hash::{DefaultHasher, Hash, Hasher}; -#[expect(dead_code)] #[derive(Clone, Copy)] pub struct Seed(pub i64);