Pumpkin/pumpkin-world/src/generation/carver/cave.rs
Alexander Medvedev 3629f43dcc fix: disable cave carver for now
fixes some issues
2026-05-23 16:36:08 +02:00

450 lines
15 KiB
Rust

use super::Carver;
use crate::ProtoChunk;
use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig, HeightProvider};
use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::random::{RandomGenerator, RandomImpl};
use std::f32::consts::PI;
pub struct CaveCarver;
impl Carver for CaveCarver {
fn carve(
&self,
config: &CarverConfig,
chunk: &mut ProtoChunk,
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),
CarverAdditionalConfig::NetherCave(ref c) => (true, c),
CarverAdditionalConfig::Canyon(_) => return,
};
let min_y = chunk.bottom_y() as i32;
let height = chunk.height();
let max_distance = (4 * 2 - 1) << 4;
let bound = if is_nether { 10 } else { 15 };
let c1 = random.next_bounded_i32(bound);
let c2 = random.next_bounded_i32(c1 + 1);
let cave_count = random.next_bounded_i32(c2 + 1);
for _ in 0..cave_count {
let x = (carver_chunk_pos.x << 4) + random.next_bounded_i32(16);
let y = get_height(&config.y, random, min_y as i8, height) as f64;
let z = (carver_chunk_pos.y << 4) + random.next_bounded_i32(16);
let horizontal_radius_multiplier =
cave_config.horizontal_radius_multiplier.get(random) as f64;
let vertical_radius_multiplier =
cave_config.vertical_radius_multiplier.get(random) as f64;
let floor_level = cave_config.floor_level.get(random) as f64;
let mut tunnels = 1;
if random.next_bounded_i32(4) == 0 {
let y_scale = config.y_scale.get(random) as f64;
let thickness = 1.0 + random.next_f32() * 6.0;
Self::create_room(
chunk,
x as f64,
y,
z as f64,
thickness,
y_scale,
config,
floor_level,
is_nether,
);
tunnels += random.next_bounded_i32(4);
}
for _ in 0..tunnels {
let horizontal_rotation = random.next_f32() * PI * 2.0;
let vertical_rotation = (random.next_f32() - 0.5) / 4.0;
let thickness = Self::get_thickness(random, is_nether);
let distance = max_distance - random.next_bounded_i32(max_distance / 4);
Self::create_tunnel(
config,
chunk,
random.next_i64(),
x as f64,
y,
z as f64,
horizontal_radius_multiplier,
vertical_radius_multiplier,
thickness,
horizontal_rotation,
vertical_rotation,
0,
distance,
if is_nether { 5.0 } else { 1.0 }, // this.getYScale()
floor_level,
is_nether,
legacy_random_source,
);
}
}
}
}
impl CaveCarver {
fn get_thickness(random: &mut RandomGenerator, is_nether: bool) -> f32 {
if is_nether {
(random.next_f32() * 2.0 + random.next_f32()) * 2.0
} else {
let mut thickness = random.next_f32() * 2.0 + random.next_f32();
if random.next_bounded_i32(10) == 0 {
thickness *= random.next_f32() * random.next_f32() * 3.0 + 1.0;
}
thickness
}
}
#[allow(clippy::too_many_arguments)]
fn create_room(
chunk: &mut ProtoChunk,
x: f64,
y: f64,
z: f64,
thickness: f32,
y_scale: f64,
config: &CarverConfig,
floor_level: f64,
is_nether: bool,
) {
let horizontal_radius = 1.5 + (PI / 2.0).sin() * thickness;
let vertical_radius = horizontal_radius as f64 * y_scale;
Self::carve_ellipsoid(
chunk,
config,
x + 1.0,
y,
z,
horizontal_radius as f64,
vertical_radius,
floor_level,
is_nether,
);
}
#[allow(clippy::too_many_arguments)]
fn create_tunnel(
config: &CarverConfig,
chunk: &mut ProtoChunk,
tunnel_seed: i64,
mut x: f64,
mut y: f64,
mut z: f64,
horizontal_radius_multiplier: f64,
vertical_radius_multiplier: f64,
thickness: f32,
mut horizontal_rotation: f32,
mut vertical_rotation: f32,
step: i32,
dist: i32,
y_scale: f64,
floor_level: f64,
is_nether: bool,
legacy_random_source: bool,
) {
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 is_steep = random.next_bounded_i32(6) == 0;
let mut y_rota = 0.0f32;
let mut x_rota = 0.0f32;
for current_step in step..dist {
let horizontal_radius =
1.5 + (PI * current_step as f32 / dist as f32).sin() * thickness;
let vertical_radius = horizontal_radius as f64 * y_scale;
let cos_x = vertical_rotation.cos();
x += (horizontal_rotation.cos() * cos_x) as f64;
y += vertical_rotation.sin() as f64;
z += (horizontal_rotation.sin() * cos_x) as f64;
vertical_rotation *= if is_steep { 0.92 } else { 0.7 };
vertical_rotation += x_rota * 0.1;
horizontal_rotation += y_rota * 0.1;
x_rota *= 0.9;
y_rota *= 0.75;
x_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 2.0;
y_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 4.0;
if current_step == split_point && thickness > 1.0 {
Self::create_tunnel(
config,
chunk,
random.next_i64(),
x,
y,
z,
horizontal_radius_multiplier,
vertical_radius_multiplier,
random.next_f32() * 0.5 + 0.5,
horizontal_rotation - (PI / 2.0),
vertical_rotation / 3.0,
current_step,
dist,
1.0,
floor_level,
is_nether,
legacy_random_source,
);
Self::create_tunnel(
config,
chunk,
random.next_i64(),
x,
y,
z,
horizontal_radius_multiplier,
vertical_radius_multiplier,
random.next_f32() * 0.5 + 0.5,
horizontal_rotation + (PI / 2.0),
vertical_rotation / 3.0,
current_step,
dist,
1.0,
floor_level,
is_nether,
legacy_random_source,
);
return;
}
if random.next_bounded_i32(4) != 0 {
if !Self::can_reach(chunk.x, chunk.z, x, z, current_step, dist, thickness) {
return;
}
Self::carve_ellipsoid(
chunk,
config,
x,
y,
z,
horizontal_radius as f64 * horizontal_radius_multiplier,
vertical_radius * vertical_radius_multiplier,
floor_level,
is_nether,
);
}
}
}
#[allow(clippy::too_many_arguments)]
fn can_reach(
chunk_x: i32,
chunk_z: i32,
x: f64,
z: f64,
step: i32,
distance: i32,
thickness: f32,
) -> bool {
let chunk_middle_x = (chunk_x << 4) + 8;
let chunk_middle_z = (chunk_z << 4) + 8;
let dx = x - chunk_middle_x as f64;
let dz = z - chunk_middle_z as f64;
let remaining = (distance - step) as f64;
let rr = (thickness + 2.0 + 16.0) as f64;
dx * dx + dz * dz - remaining * remaining <= rr * rr
}
#[allow(clippy::too_many_arguments)]
fn carve_ellipsoid(
chunk: &mut ProtoChunk,
config: &CarverConfig,
x: f64,
y: f64,
z: f64,
horizontal_radius: f64,
vertical_radius: f64,
floor_level: f64,
is_nether: bool,
) {
let center_x = (chunk.x << 4) as f64 + 8.0;
let center_z = (chunk.z << 4) as f64 + 8.0;
let max_delta = 16.0 + horizontal_radius * 2.0;
if (x - center_x).abs() > max_delta || (z - center_z).abs() > max_delta {
return;
}
let chunk_min_x = chunk.x << 4;
let chunk_min_z = chunk.z << 4;
let min_x_idx = ((x - horizontal_radius).floor() as i32 - chunk_min_x - 1).max(0);
let max_x_idx = ((x + horizontal_radius).floor() as i32 - chunk_min_x).min(15);
let min_y = ((y - vertical_radius).floor() as i32 - 1).max(chunk.bottom_y() as i32 + 1);
let protected_blocks_on_top = 7;
let max_y = ((y + vertical_radius).floor() as i32 + 1)
.min(chunk.bottom_y() as i32 + chunk.height() as i32 - 1 - protected_blocks_on_top);
let min_z_idx = ((z - horizontal_radius).floor() as i32 - chunk_min_z - 1).max(0);
let max_z_idx = ((z + horizontal_radius).floor() as i32 - chunk_min_z).min(15);
for x_index in min_x_idx..=max_x_idx {
let world_x = chunk_min_x + x_index;
let xd = (world_x as f64 + 0.5 - x) / horizontal_radius;
for z_index in min_z_idx..=max_z_idx {
let world_z = chunk_min_z + z_index;
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 + 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);
Self::carve_block(
chunk,
config,
world_x,
world_y,
world_z,
is_nether,
&mut has_grass,
);
}
}
}
}
}
}
fn should_skip(xd: f64, yd: f64, zd: f64, floor_level: f64) -> bool {
if yd <= floor_level {
true
} else {
xd * xd + yd * yd + zd * zd >= 1.0
}
}
#[allow(clippy::too_many_arguments)]
fn carve_block(
chunk: &mut ProtoChunk,
config: &CarverConfig,
x: i32,
y: i32,
z: i32,
is_nether: bool,
has_grass: &mut bool,
) -> bool {
let local_y = y - chunk.bottom_y() as i32;
let state = chunk.get_block_state(&Vector3::new(x, y, z));
let block = state.to_block();
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) {
return false;
}
let carve_state = {
let lava_y = if is_nether {
chunk.bottom_y() as i32 + 31
} else {
config
.lava_level
.get_y(chunk.bottom_y() as i16, chunk.height())
};
if y <= lava_y {
Some(pumpkin_data::Block::LAVA.default_state)
} else {
// TODO: Aquifer logic goes here.
// BlockState state = aquifer.computeSubstance(...)
// return state (or debug barrier if null)
if block.id == pumpkin_data::Block::WATER.id
|| block.id == pumpkin_data::Block::LAVA.id
{
None
} else {
Some(pumpkin_data::Block::AIR.default_state)
}
}
};
if let Some(state) = carve_state {
chunk.set_block_state(x, local_y, z, state);
// TODO: Fluid scheduling
// if aquifer.should_schedule_fluid_update() && !state.fluid_state().is_empty() {
// chunk.mark_pos_for_postprocessing(x, y, z);
// }
// TODO: fix this (grass block survival logic)
// if *has_grass {
// let below_state_id = chunk.get_block_state_raw(x, local_y - 1, z);
// let below_block = pumpkin_data::Block::from_state_id(below_state_id);
// if below_block.id == pumpkin_data::Block::DIRT.id {
// let top_material =
// pumpkin_data::Block::GRASS_BLOCK.default_state;
// chunk.set_block_state(x, local_y - 1, z, top_material);
// }
// }
return true;
}
false
}
}
pub fn get_height(p: &HeightProvider, random: &mut RandomGenerator, min_y: i8, height: u16) -> i32 {
match p {
HeightProvider::Uniform(p) => {
let min = p.min_inclusive.get_y(min_y as i16, height);
let max = p.max_inclusive.get_y(min_y as i16, height);
random.next_inbetween_i32(min, max)
}
HeightProvider::Trapezoid(p) => {
let i = p.min_inclusive.get_y(min_y as i16, height);
let j = p.max_inclusive.get_y(min_y as i16, height);
let plateau = p.plateau.unwrap_or(0);
let k = j - i;
if plateau >= k {
random.next_inbetween_i32(i, j)
} else {
let l = (k - plateau) / 2;
let m = k - l;
i + random.next_inbetween_i32(0, m) + random.next_inbetween_i32(0, l)
}
}
HeightProvider::VeryBiasedToBottom(p) => {
let min = p.min_inclusive.get_y(min_y as i16, height);
let max = p.max_inclusive.get_y(min_y as i16, height);
let inner = p.inner.map_or(1, std::num::NonZero::get) as i32;
let min_rnd = random.next_inbetween_i32(min + inner, max);
let max_rnd = random.next_inbetween_i32(min, min_rnd - 1);
random.next_inbetween_i32(min, max_rnd - 1 + inner)
}
}
}