feat: add chunk blender

This commit is contained in:
Alexander Medvedev
2026-05-03 11:11:42 +02:00
parent f2c1d8407a
commit af10507137
16 changed files with 496 additions and 116 deletions

View File

@@ -226,6 +226,7 @@ impl PerlinNoiseSampler {
}
/// Data for a single octave in an octave Perlin noise sampler.
#[derive(Clone)]
pub struct SamplerData {
/// The Perlin noise sampler for this octave.
pub sampler: PerlinNoiseSampler,
@@ -242,6 +243,7 @@ pub struct SamplerData {
/// Octave noise, also known as fractal noise, combines multiple octaves of Perlin noise
/// with different frequencies and amplitudes to create more complex, natural-looking patterns.
/// Each octave adds finer detail to the overall noise.
#[derive(Clone)]
pub struct OctavePerlinNoiseSampler {
/// The list of samplers for each octave, with their associated parameters.
pub samplers: Box<[SamplerData]>,

View File

@@ -149,23 +149,22 @@ pub fn get_region_seed(world_seed: u64, region_x: i32, region_z: i32, salt: u32)
/// Carver seeds are used for terrain carving features like caves and ravines.
///
/// # Arguments
/// - `random` The random number generator to use.
/// - `world_seed` The base world seed.
/// - `world_seed` The base world seed (plus carver index).
/// - `chunk_x` The X chunk coordinate.
/// - `chunk_z` The Z chunk coordinate.
///
/// # Returns
/// A carver seed for the given chunk.
#[inline]
pub fn get_carver_seed(
random: &mut RandomGenerator,
world_seed: u64,
chunk_x: i32,
chunk_z: i32,
) -> u64 {
let x = random.next_i64();
let z = random.next_i64();
(chunk_x as u64).wrapping_mul(x as u64) ^ (chunk_z as u64).wrapping_mul(z as u64) ^ world_seed
#[must_use]
pub fn get_carver_seed(world_seed: u64, chunk_x: i32, chunk_z: i32) -> u64 {
let mut random = LegacyRand::from_seed(world_seed);
let l = random.next_i64() | 1;
let m = random.next_i64() | 1;
((chunk_x as i64)
.wrapping_mul(l)
.wrapping_add((chunk_z as i64).wrapping_mul(m)) as u64)
^ world_seed
}
#[enum_dispatch]

View File

@@ -193,6 +193,7 @@ impl ChunkData {
light_engine: std::sync::Mutex::new(light_engine),
light_populated: AtomicBool::new(chunk_data.light_correct),
status: chunk_data.status,
blending_data: None,
})
}

View File

@@ -80,6 +80,7 @@ pub struct ChunkData {
pub light_engine: std::sync::Mutex<ChunkLight>,
pub light_populated: AtomicBool,
pub status: ChunkStatus,
pub blending_data: Option<crate::generation::blender::blending_data::BlendingData>,
pub dirty: AtomicBool,
}

View File

@@ -229,6 +229,7 @@ impl Chunk {
light_engine: Mutex::new(ChunkLight::default()),
light_populated: AtomicBool::new(false),
status: ChunkStatus::Empty,
blending_data: None,
dirty: AtomicBool::new(false),
})),
) {
@@ -317,6 +318,7 @@ impl Chunk {
fluid_ticks: Default::default(),
block_entities: Mutex::new(block_entities),
status: proto_chunk.stage.into(),
blending_data: proto_chunk.blending_data.clone(),
};
chunk.heightmap = Mutex::new(chunk.calculate_heightmap());

View File

@@ -323,6 +323,24 @@ impl GenerationCache for Cache {
}
}
fn get_blending_data(
&self,
chunk_x: i32,
chunk_z: i32,
) -> Option<&crate::generation::blender::blending_data::BlendingData> {
let dx = chunk_x - self.x;
let dz = chunk_z - self.z;
if dx < 0 || dx >= self.size || dz < 0 || dz >= self.size {
return None;
}
match &self.chunks[(dx * self.size + dz) as usize] {
Chunk::Proto(chunk) => chunk.blending_data.as_ref(),
Chunk::Level(data) => data.blending_data.as_ref(),
}
}
fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
is_air(GenerationCache::get_block_state(self, local_pos).0)
}

View File

@@ -0,0 +1,72 @@
use pumpkin_data::chunk::Biome;
#[derive(Clone)]
pub struct BlendingData {
pub min_y: i32,
pub max_y: i32,
// Heights at quart positions (16x16 per chunk)
pub heights: Vec<f64>,
// Densities at quart positions (16x16 per chunk, but at what Y levels? 1.18+ uses 4 blocks quart)
// Simplified: only store for current chunk's Y levels
pub densities: Vec<f64>,
// Biomes at quart positions (16x16 per chunk)
pub biomes: Vec<&'static Biome>,
}
impl BlendingData {
pub fn get_height(&self, cell_x: i32, _cell_y: i32, cell_z: i32) -> f64 {
if !(0..16).contains(&cell_x) || !(0..16).contains(&cell_z) {
return f64::MAX;
}
self.heights[(cell_z * 16 + cell_x) as usize]
}
pub fn get_density(&self, cell_x: i32, _cell_y: i32, cell_z: i32) -> f64 {
// cell_y is block_y / 8 in Blender.java
// We'll need a better storage for this if we want full fidelity
if !(0..16).contains(&cell_x) || !(0..16).contains(&cell_z) {
return f64::MAX;
}
// Dummy implementation
f64::MAX
}
pub fn iterate_heights<F>(&self, quart_x: i32, quart_z: i32, mut consumer: F)
where
F: FnMut(i32, i32, f64),
{
for z in 0..16 {
for x in 0..16 {
let h = self.heights[z * 16 + x];
if h != f64::MAX {
consumer(quart_x + x as i32, quart_z + z as i32, h);
}
}
}
}
pub fn iterate_densities<F>(
&self,
_quart_x: i32,
_quart_z: i32,
_min_cell_y: i32,
_max_cell_y: i32,
_consumer: F,
) where
F: FnMut(i32, i32, i32, f64),
{
// TODO: implement density iteration
}
pub fn iterate_biomes<F>(&self, quart_x: i32, _quart_y: i32, quart_z: i32, mut consumer: F)
where
F: FnMut(i32, i32, &'static Biome),
{
for z in 0..16 {
for x in 0..16 {
let biome = self.biomes[z * 16 + x];
consumer(quart_x + x as i32, quart_z + z as i32, biome);
}
}
}
}

View File

@@ -1,62 +1,361 @@
use enum_dispatch::enum_dispatch;
pub mod blending_data;
use std::f64;
use crate::biome::BiomeSupplier;
use crate::generation::biome_coords;
use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSampler;
use crate::generation::proto_chunk::GenerationCache;
use blending_data::BlendingData;
use pumpkin_data::chunk::Biome;
use pumpkin_data::noise_parameter::DoublePerlinNoiseParameters;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::random::xoroshiro128::Xoroshiro;
use rustc_hash::FxHashMap;
use crate::{
biome::BiomeSupplier, generation::noise::router::multi_noise_sampler::MultiNoiseSampler,
};
pub struct BlendResult {
alpha: f64,
offset: f64,
pub struct BlendingOutput {
pub alpha: f64,
pub blending_offset: f64,
}
impl BlendResult {
pub const fn new(alpha: f64, offset: f64) -> Self {
Self { alpha, offset }
}
pub struct Blender {
height_and_biome_blending_data: FxHashMap<u64, BlendingData>,
density_blending_data: FxHashMap<u64, BlendingData>,
}
#[enum_dispatch(BlenderImpl)]
pub enum Blender {
NoBlend(NoBlendBlender),
}
const HEIGHT_BLENDING_RANGE_CELLS: i32 = (7 << 2) - 1; // QuartPos.fromSection(7) - 1
const HEIGHT_BLENDING_RANGE_CHUNKS: i32 = (HEIGHT_BLENDING_RANGE_CELLS + 3) >> 2; // QuartPos.toSection(...)
const DENSITY_BLENDING_RANGE_CELLS: i32 = 2;
const DENSITY_BLENDING_RANGE_CHUNKS: i32 = 5 >> 2; // QuartPos.toSection(5)
impl Blender {
pub const NO_BLEND: Self = Self::NoBlend(NoBlendBlender {});
pub fn empty() -> Self {
Self {
height_and_biome_blending_data: FxHashMap::default(),
density_blending_data: FxHashMap::default(),
}
}
pub fn of<C: GenerationCache>(cache: &C) -> Self {
let center_chunk = cache.get_center_chunk();
let center_x = center_chunk.x;
let center_z = center_chunk.z;
let mut height_and_biome_data = FxHashMap::default();
let mut density_data = FxHashMap::default();
let max_dist_sq = (HEIGHT_BLENDING_RANGE_CHUNKS + 1) * (HEIGHT_BLENDING_RANGE_CHUNKS + 1);
for dx in -HEIGHT_BLENDING_RANGE_CHUNKS..=HEIGHT_BLENDING_RANGE_CHUNKS {
for dz in -HEIGHT_BLENDING_RANGE_CHUNKS..=HEIGHT_BLENDING_RANGE_CHUNKS {
if dx * dx + dz * dz <= max_dist_sq {
let chunk_x = center_x + dx;
let chunk_z = center_z + dz;
if let Some(blending_data) = cache.get_blending_data(chunk_x, chunk_z) {
let packed = (chunk_x as u32 as u64) | ((chunk_z as u32 as u64) << 32);
height_and_biome_data.insert(packed, blending_data.clone());
if (-DENSITY_BLENDING_RANGE_CHUNKS..=DENSITY_BLENDING_RANGE_CHUNKS)
.contains(&dx)
&& (-DENSITY_BLENDING_RANGE_CHUNKS..=DENSITY_BLENDING_RANGE_CHUNKS)
.contains(&dz)
{
density_data.insert(packed, blending_data.clone());
}
}
}
}
}
if height_and_biome_data.is_empty() && density_data.is_empty() {
Self::empty()
} else {
Self {
height_and_biome_blending_data: height_and_biome_data,
density_blending_data: density_data,
}
}
}
pub fn is_empty(&self) -> bool {
self.height_and_biome_blending_data.is_empty() && self.density_blending_data.is_empty()
}
pub fn blend_offset_and_factor(&self, block_x: i32, block_z: i32) -> BlendingOutput {
let cell_x = biome_coords::from_block(block_x);
let cell_z = biome_coords::from_block(block_z);
let fixed_height = self
.get_blending_data_value(cell_x, 0, cell_z, |data, x, y, z| data.get_height(x, y, z));
if fixed_height != f64::MAX {
return BlendingOutput {
alpha: 0.0,
blending_offset: Self::height_to_offset(fixed_height),
};
}
let mut total_weight = 0.0;
let mut weighted_heights = 0.0;
let mut closest_distance = f64::INFINITY;
for (&packed_pos, blending_data) in &self.height_and_biome_blending_data {
let chunk_x = (packed_pos & 0xFFFFFFFF) as i32;
let chunk_z = (packed_pos >> 32) as i32;
blending_data.iterate_heights(
biome_coords::from_chunk(chunk_x),
biome_coords::from_chunk(chunk_z),
|test_cell_x, test_cell_z, height| {
let dx = (cell_x - test_cell_x) as f64;
let dz = (cell_z - test_cell_z) as f64;
let distance = (dx * dx + dz * dz).sqrt();
if distance <= HEIGHT_BLENDING_RANGE_CELLS as f64 {
if distance < closest_distance {
closest_distance = distance;
}
let weight = 1.0 / (distance * distance * distance * distance);
weighted_heights += height * weight;
total_weight += weight;
}
},
);
}
if closest_distance == f64::INFINITY {
BlendingOutput {
alpha: 1.0,
blending_offset: 0.0,
}
} else {
let average_height = weighted_heights / total_weight;
let mut alpha =
(closest_distance / (HEIGHT_BLENDING_RANGE_CELLS + 1) as f64).clamp(0.0, 1.0);
alpha = 3.0 * alpha * alpha - 2.0 * alpha * alpha * alpha;
BlendingOutput {
alpha,
blending_offset: Self::height_to_offset(average_height),
}
}
}
fn height_to_offset(height: f64) -> f64 {
let target_y = height + 0.5;
let target_y_mod = target_y.rem_euclid(8.0);
(32.0 * (target_y - 128.0) - 3.0 * (target_y - 120.0) * target_y_mod
+ 3.0 * target_y_mod * target_y_mod)
/ (128.0 * (32.0 - 3.0 * target_y_mod))
}
pub fn blend_density(&self, block_x: i32, block_y: i32, block_z: i32, noise_value: f64) -> f64 {
let cell_x = biome_coords::from_block(block_x);
let cell_y = block_y / 8;
let cell_z = biome_coords::from_block(block_z);
let fixed_density =
self.get_blending_data_value(cell_x, cell_y, cell_z, |data, x, y, z| {
data.get_density(x, y, z)
});
if fixed_density != f64::MAX {
return fixed_density;
}
let mut total_weight = 0.0;
let mut weighted_densities = 0.0;
let mut closest_distance = f64::INFINITY;
for (&packed_pos, blending_data) in &self.density_blending_data {
let chunk_x = (packed_pos & 0xFFFFFFFF) as i32;
let chunk_z = (packed_pos >> 32) as i32;
blending_data.iterate_densities(
biome_coords::from_chunk(chunk_x),
biome_coords::from_chunk(chunk_z),
cell_y - 1,
cell_y + 1,
|test_cell_x, test_cell_y, test_cell_z, density| {
let dx = (cell_x - test_cell_x) as f64;
let dy = ((cell_y - test_cell_y) * 2) as f64;
let dz = (cell_z - test_cell_z) as f64;
let distance = (dx * dx + dy * dy + dz * dz).sqrt();
if distance <= 2.0 {
if distance < closest_distance {
closest_distance = distance;
}
let weight = 1.0 / (distance * distance * distance * distance);
weighted_densities += density * weight;
total_weight += weight;
}
},
);
}
if closest_distance == f64::INFINITY {
noise_value
} else {
let average_density = weighted_densities / total_weight;
let alpha = (closest_distance / 3.0).clamp(0.0, 1.0);
alpha * noise_value + (1.0 - alpha) * average_density
}
}
fn get_blending_data_value<F>(&self, cell_x: i32, cell_y: i32, cell_z: i32, getter: F) -> f64
where
F: Fn(&BlendingData, i32, i32, i32) -> f64,
{
let chunk_x = biome_coords::to_chunk(cell_x);
let chunk_z = biome_coords::to_chunk(cell_z);
let min_x = (cell_x & 3) == 0;
let min_z = (cell_z & 3) == 0;
let mut value = self.get_data_value(&getter, chunk_x, chunk_z, cell_x, cell_y, cell_z);
if value == f64::MAX {
if min_x && min_z {
value =
self.get_data_value(&getter, chunk_x - 1, chunk_z - 1, cell_x, cell_y, cell_z);
}
if value == f64::MAX {
if min_x {
value =
self.get_data_value(&getter, chunk_x - 1, chunk_z, cell_x, cell_y, cell_z);
}
if value == f64::MAX && min_z {
value =
self.get_data_value(&getter, chunk_x, chunk_z - 1, cell_x, cell_y, cell_z);
}
}
}
value
}
fn get_data_value<F>(
&self,
getter: &F,
chunk_x: i32,
chunk_z: i32,
cell_x: i32,
cell_y: i32,
cell_z: i32,
) -> f64
where
F: Fn(&BlendingData, i32, i32, i32) -> f64,
{
let packed = (chunk_x as u32 as u64) | ((chunk_z as u32 as u64) << 32);
if let Some(data) = self.height_and_biome_blending_data.get(&packed) {
getter(
data,
cell_x - biome_coords::from_chunk(chunk_x),
cell_y,
cell_z - biome_coords::from_chunk(chunk_z),
)
} else {
f64::MAX
}
}
pub fn blend_biome(
&self,
quart_x: i32,
quart_y: i32,
quart_z: i32,
shift_noise: &DoublePerlinNoiseSampler,
) -> Option<&'static Biome> {
let mut closest_distance = f64::INFINITY;
let mut closest_biome = None;
for (&packed_pos, blending_data) in &self.height_and_biome_blending_data {
let chunk_x = (packed_pos & 0xFFFFFFFF) as i32;
let chunk_z = (packed_pos >> 32) as i32;
blending_data.iterate_biomes(
biome_coords::from_chunk(chunk_x),
quart_y,
biome_coords::from_chunk(chunk_z),
|test_cell_x, test_cell_z, biome| {
let dx = (quart_x - test_cell_x) as f64;
let dz = (quart_z - test_cell_z) as f64;
let distance = (dx * dx + dz * dz).sqrt();
if distance <= HEIGHT_BLENDING_RANGE_CELLS as f64 && distance < closest_distance
{
closest_biome = Some(biome);
closest_distance = distance;
}
},
);
}
if closest_distance == f64::INFINITY {
None
} else {
let shift = shift_noise.sample(quart_x as f64, 0.0, quart_z as f64) * 12.0;
let alpha = ((closest_distance + shift) / (HEIGHT_BLENDING_RANGE_CELLS + 1) as f64)
.clamp(0.0, 1.0);
if alpha > 0.5 { None } else { closest_biome }
}
}
}
pub struct BlenderBiomeSupplier<'a> {
base: &'a dyn BiomeSupplier,
blender: &'a Blender,
shift_noise: DoublePerlinNoiseSampler,
}
impl BiomeSupplier for BlenderBiomeSupplier<'_> {
fn biome(&self, x: i32, y: i32, z: i32, sampler: &mut MultiNoiseSampler<'_>) -> &'static Biome {
self.base.biome(x, y, z, sampler)
if let Some(blended) = self.blender.blend_biome(x, y, z, &self.shift_noise) {
blended
} else {
self.base.biome(x, y, z, sampler)
}
}
}
#[enum_dispatch]
pub trait BlenderImpl {
fn calculate(&self, block_x: i32, block_z: i32) -> BlendResult;
fn blend_offset_and_factor(&self, block_x: i32, block_z: i32) -> BlendingOutput;
fn apply_blend_density(&self, pos: &Vector3<i32>, density: f64) -> f64;
fn blend_density(&self, pos: &Vector3<i32>, density: f64) -> f64;
fn get_biome_supplier<'a>(&self, supplier: &'a dyn BiomeSupplier) -> BlenderBiomeSupplier<'a>;
fn get_biome_supplier<'a>(
&'a self,
supplier: &'a dyn BiomeSupplier,
) -> BlenderBiomeSupplier<'a>;
}
pub struct NoBlendBlender {}
impl BlenderImpl for NoBlendBlender {
fn calculate(&self, _block_x: i32, _block_z: i32) -> BlendResult {
BlendResult::new(1f64, 1f64)
impl BlenderImpl for Blender {
fn blend_offset_and_factor(&self, block_x: i32, block_z: i32) -> BlendingOutput {
self.blend_offset_and_factor(block_x, block_z)
}
fn apply_blend_density(&self, _pos: &Vector3<i32>, density: f64) -> f64 {
density
fn blend_density(&self, pos: &Vector3<i32>, density: f64) -> f64 {
self.blend_density(pos.x, pos.y, pos.z, density)
}
fn get_biome_supplier<'a>(&self, supplier: &'a dyn BiomeSupplier) -> BlenderBiomeSupplier<'a> {
BlenderBiomeSupplier { base: supplier }
fn get_biome_supplier<'a>(
&'a self,
supplier: &'a dyn BiomeSupplier,
) -> BlenderBiomeSupplier<'a> {
let mut random = Xoroshiro::from_seed(42);
let shift_noise = DoublePerlinNoiseSampler::from_params(
&mut random,
&DoublePerlinNoiseParameters::OFFSET,
false,
);
BlenderBiomeSupplier {
base: supplier,
blender: self,
shift_noise,
}
}
}

View File

@@ -17,6 +17,7 @@ impl Carver for CanyonCarver {
random: &mut RandomGenerator,
_chunk_pos: &Vector2<i32>,
carver_chunk_pos: &Vector2<i32>,
legacy_random_source: bool,
) {
let CarverAdditionalConfig::Canyon(ref canyon_config) = config.additional else {
return;
@@ -51,6 +52,7 @@ impl Carver for CanyonCarver {
0,
distance,
y_scale,
legacy_random_source,
);
}
}
@@ -71,10 +73,17 @@ impl CanyonCarver {
step: i32,
distance: i32,
y_scale: f64,
legacy_random_source: bool,
) {
let mut random = RandomGenerator::Xoroshiro(
pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(tunnel_seed as u64),
);
let mut random = if legacy_random_source {
RandomGenerator::Legacy(pumpkin_util::random::legacy_rand::LegacyRand::from_seed(
tunnel_seed as u64,
))
} else {
RandomGenerator::Xoroshiro(pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(
tunnel_seed as u64,
))
};
let width_factor_per_height =
self.init_width_factors(chunk.height() as usize, config, &mut random);
let mut y_rota = 0.0f32;
@@ -234,8 +243,7 @@ impl CanyonCarver {
let zd = (world_z as f64 + 0.5 - z) / horizontal_radius;
if xd * xd + zd * zd < 1.0 {
let mut has_grass = false;
for world_y in (min_y..=max_y).rev() {
for world_y in (min_y + 1..=max_y).rev() {
let yd = (world_y as f64 - 0.5 - y) / vertical_radius;
if !self.should_skip(
@@ -248,8 +256,7 @@ impl CanyonCarver {
) && !chunk.carving_mask.get(world_x, world_y, world_z)
{
chunk.carving_mask.set(world_x, world_y, world_z);
has_grass |= self
.carve_block(chunk, config, world_x, world_y, world_z, has_grass);
self.carve_block(chunk, config, world_x, world_y, world_z);
}
}
}
@@ -280,7 +287,6 @@ impl CanyonCarver {
x: i32,
y: i32,
z: i32,
mut has_grass: bool,
) -> bool {
let local_y = y - chunk.bottom_y() as i32;
let state_id = chunk.get_block_state_raw(x & 15, local_y, z & 15);
@@ -290,12 +296,6 @@ impl CanyonCarver {
return false;
}
if block.id == pumpkin_data::Block::GRASS_BLOCK.id
|| block.id == pumpkin_data::Block::MYCELIUM.id
{
has_grass = true;
}
if config.replaceable.1.contains(&block.id) {
let air = BlockState::from_id(pumpkin_data::Block::AIR.default_state.id);
let lava = BlockState::from_id(pumpkin_data::Block::LAVA.default_state.id);
@@ -310,18 +310,6 @@ impl CanyonCarver {
chunk.set_block_state(x & 15, local_y, z & 15, air);
}
if has_grass {
let down_y = y - 1;
let local_down_y = down_y - chunk.bottom_y() as i32;
if (0..chunk.height() as i32).contains(&local_down_y) {
let down_state_id = chunk.get_block_state_raw(x & 15, local_down_y, z & 15);
if pumpkin_data::Block::from_state_id(down_state_id).id
== pumpkin_data::Block::DIRT.id
{
// dirt replacement skipped
}
}
}
return true;
}
false

View File

@@ -16,6 +16,7 @@ impl Carver for CaveCarver {
random: &mut RandomGenerator,
_chunk_pos: &Vector2<i32>,
carver_chunk_pos: &Vector2<i32>,
legacy_random_source: bool,
) {
let (is_nether, cave_config) = match config.additional {
CarverAdditionalConfig::Cave(ref c) => (false, c),
@@ -85,6 +86,7 @@ impl Carver for CaveCarver {
if is_nether { 5.0 } else { 1.0 }, // this.getYScale()
floor_level,
is_nether,
legacy_random_source,
);
}
}
@@ -151,10 +153,17 @@ impl CaveCarver {
y_scale: f64,
floor_level: f64,
is_nether: bool,
legacy_random_source: bool,
) {
let mut random = RandomGenerator::Xoroshiro(
pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(tunnel_seed as u64),
);
let mut random = if legacy_random_source {
RandomGenerator::Legacy(pumpkin_util::random::legacy_rand::LegacyRand::from_seed(
tunnel_seed as u64,
))
} else {
RandomGenerator::Xoroshiro(pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(
tunnel_seed as u64,
))
};
let split_point = random.next_bounded_i32(dist / 2) + dist / 4;
let steep = random.next_bounded_i32(6) == 0;
let mut y_rota = 0.0f32;
@@ -195,6 +204,7 @@ impl CaveCarver {
1.0,
floor_level,
is_nether,
legacy_random_source,
);
self.create_tunnel(
config,
@@ -213,6 +223,7 @@ impl CaveCarver {
1.0,
floor_level,
is_nether,
legacy_random_source,
);
return;
}
@@ -301,17 +312,14 @@ impl CaveCarver {
let zd = (world_z as f64 + 0.5 - z) / horizontal_radius;
if xd * xd + zd * zd < 1.0 {
let mut has_grass = false;
for world_y in (min_y..=max_y).rev() {
let yd = (world_y as f64 + 0.5 - y) / vertical_radius;
for world_y in (min_y + 1..=max_y).rev() {
let yd = (world_y as f64 - 0.5 - y) / vertical_radius;
if !self.should_skip(xd, yd, zd, floor_level)
&& !chunk.carving_mask.get(world_x, world_y, world_z)
{
chunk.carving_mask.set(world_x, world_y, world_z);
has_grass |= self.carve_block(
chunk, config, world_x, world_y, world_z, is_nether, has_grass,
);
self.carve_block(chunk, config, world_x, world_y, world_z, is_nether);
}
}
}
@@ -336,7 +344,6 @@ impl CaveCarver {
y: i32,
z: i32,
is_nether: bool,
mut has_grass: bool,
) -> bool {
let local_y = y - chunk.bottom_y() as i32;
let state_id = chunk.get_block_state_raw(x & 15, local_y, z & 15);
@@ -346,12 +353,6 @@ impl CaveCarver {
return false;
}
if block.id == pumpkin_data::Block::GRASS_BLOCK.id
|| block.id == pumpkin_data::Block::MYCELIUM.id
{
has_grass = true;
}
// Only carve if it's replaceable
if config.replaceable.1.contains(&block.id) {
// Replace with air or lava
@@ -372,19 +373,6 @@ impl CaveCarver {
chunk.set_block_state(x & 15, local_y, z & 15, air);
}
if has_grass {
let down_y = y - 1;
let local_down_y = down_y - chunk.bottom_y() as i32;
if (0..chunk.height() as i32).contains(&local_down_y) {
let down_state_id = chunk.get_block_state_raw(x & 15, local_down_y, z & 15);
if pumpkin_data::Block::from_state_id(down_state_id).id
== pumpkin_data::Block::DIRT.id
{
// dirt replacement skipped
}
}
}
return true;
}
false

View File

@@ -17,14 +17,11 @@ pub trait Carver {
random: &mut RandomGenerator,
chunk_pos: &Vector2<i32>,
carver_chunk_pos: &Vector2<i32>,
legacy_random_source: bool,
);
}
pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
let mut random = RandomGenerator::Xoroshiro(
pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(generator.random_config.seed),
);
// Vanilla applyCarvers uses a range of 8 chunks (17x17 area)
let radius = 8;
let chunk_x = chunk.x;
@@ -32,7 +29,6 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
let chunk_pos = Vector2::new(chunk_x, chunk_z);
let overworld_carvers = [&CAVE, &CAVE_EXTRA_UNDERGROUND, &CANYON];
let nether_carvers = [&NETHER_CAVE];
let carvers_to_use = if generator.dimension == pumpkin_data::dimension::Dimension::OVERWORLD {
@@ -56,14 +52,19 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
// maintain the random seed logic.
for (index, &config) in carvers_to_use.iter().enumerate() {
let seed = get_carver_seed(
&mut random,
generator.random_config.seed + index as u64,
carver_x,
carver_z,
);
let mut carver_random = RandomGenerator::Xoroshiro(
pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(seed),
);
let mut carver_random = if generator.settings.legacy_random_source {
RandomGenerator::Legacy(
pumpkin_util::random::legacy_rand::LegacyRand::from_seed(seed),
)
} else {
RandomGenerator::Xoroshiro(
pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(seed),
)
};
if should_carve(config, &mut carver_random) {
match config.additional {
@@ -74,6 +75,7 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
&mut carver_random,
&chunk_pos,
&carver_chunk_pos,
generator.settings.legacy_random_source,
);
}
CarverAdditionalConfig::Canyon(_) => {
@@ -83,6 +85,7 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
&mut carver_random,
&chunk_pos,
&carver_chunk_pos,
generator.settings.legacy_random_source,
);
}
}

View File

@@ -1,7 +1,7 @@
#![allow(dead_code)]
mod biome;
mod blender;
pub mod blender;
mod block_predicate;
mod block_state_provider;
pub mod carver;

View File

@@ -1,6 +1,7 @@
use pumpkin_data::chunk::DoublePerlinNoiseParameters;
use pumpkin_util::{noise::perlin::OctavePerlinNoiseSampler, random::RandomImpl};
#[derive(Clone)]
pub struct DoublePerlinNoiseSampler {
first_sampler: OctavePerlinNoiseSampler,
second_sampler: OctavePerlinNoiseSampler,

View File

@@ -85,6 +85,11 @@ pub trait GenerationCache: HeightLimitView + BlockAccessor {
fn ocean_floor_height_exclusive(&self, x: i32, z: i32) -> i32;
fn is_air(&self, local_pos: &Vector3<i32>) -> bool;
fn get_biome_for_terrain_gen(&self, x: i32, y: i32, z: i32) -> &'static Biome;
fn get_blending_data(
&self,
chunk_x: i32,
chunk_z: i32,
) -> Option<&crate::generation::blender::blending_data::BlendingData>;
}
const AIR_BLOCK: Block = Block::AIR;
@@ -169,6 +174,7 @@ pub struct ProtoChunk {
pub stage: StagedChunkEnum,
pub light: ChunkLight,
pub carving_mask: crate::generation::carver::mask::CarvingMask,
pub blending_data: Option<crate::generation::blender::blending_data::BlendingData>,
/// Block entities pending creation when the chunk is finalized.
/// These are created from structure templates during world generation.
pub pending_block_entities: Vec<NbtCompound>,
@@ -251,6 +257,7 @@ impl ProtoChunk {
height as i32,
dimension.min_y,
),
blending_data: None,
pending_block_entities: Vec::new(),
}
}
@@ -262,6 +269,7 @@ impl ProtoChunk {
let mut proto_chunk = Self::new(chunk_data.x, chunk_data.z, generator);
proto_chunk.light = chunk_data.light_engine.lock().unwrap().clone();
proto_chunk.blending_data = chunk_data.blending_data.clone();
let section_data = &chunk_data.section;
let heightmap_data = chunk_data.heightmap.lock().unwrap();
@@ -663,7 +671,8 @@ impl ProtoChunk {
ActiveSupplier::Nether(s) => s,
ActiveSupplier::Overworld(s) => s,
};
let biome_supplier = Blender::NO_BLEND.get_biome_supplier(base_supplier);
let blender = Blender::empty();
let biome_supplier = blender.get_biome_supplier(base_supplier);
let min_y = self.bottom_y();
let bottom_section = section_coords::block_to_section(min_y as i32);
let top_section = section_coords::block_to_section(min_y as i32 + self.height() as i32 - 1);
@@ -1135,9 +1144,7 @@ impl ProtoChunk {
}
let mut candidates = set.structures.to_vec();
let mut random: RandomGenerator =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed));
let carver_seed = get_carver_seed(&mut random, seed, self.x, self.z);
let carver_seed = get_carver_seed(seed, self.x, self.z);
let mut random: RandomGenerator =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed));
@@ -1219,7 +1226,8 @@ impl ProtoChunk {
ActiveSupplier::Nether(s) => s,
ActiveSupplier::Overworld(s) => s,
};
let biome_supplier = Blender::NO_BLEND.get_biome_supplier(base_supplier);
let blender = Blender::empty();
let biome_supplier = blender.get_biome_supplier(base_supplier);
// Use an empty offset sampler since we are querying arbitrary world coordinates
let multi_noise_config = MultiNoiseSamplerBuilderOptions::new(0, 0, 0);
let mut multi_noise_sampler =

View File

@@ -213,8 +213,7 @@ fn should_generate_frequency(
random.next_f32() < frequency
}
FrequencyReductionMethod::LegacyType3 => {
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed as u64));
let carver_seed = get_carver_seed(&mut random, seed as u64, chunk_x, chunk_z);
let carver_seed = get_carver_seed(seed as u64, chunk_x, chunk_z);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed));
random.next_f64() < f64::from(frequency)
}

View File

@@ -578,8 +578,7 @@ pub struct StructureGeneratorContext {
#[must_use]
pub fn create_chunk_random(seed: i64, chunk_x: i32, chunk_z: i32) -> RandomGenerator {
let mut random: RandomGenerator = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed as u64));
let carver_seed = get_carver_seed(&mut random, seed as u64, chunk_x, chunk_z);
let carver_seed = get_carver_seed(seed as u64, chunk_x, chunk_z);
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed))
}