mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: add chunk blender
This commit is contained in:
@@ -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]>,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
72
pumpkin-world/src/generation/blender/blending_data.rs
Normal file
72
pumpkin-world/src/generation/blender/blending_data.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod biome;
|
||||
mod blender;
|
||||
pub mod blender;
|
||||
mod block_predicate;
|
||||
mod block_state_provider;
|
||||
pub mod carver;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user