chunkgen 0.2.0

Minecraft world generation library
Documentation
use std::f64::consts::PI;

use serde::Deserialize;

use crate::util::math::{cos, sin};
use crate::util::pos::ChunkPos;
use crate::util::rng::RandomSource;
use crate::util::rng::legacy_random::LegacyRandomSource;
use crate::util::value_providers::{FloatProvider, HeightProvider, IntProvider};
use crate::world::chunk::carving_mask::CarvingMask;
use crate::world::worldgen::worldgen_context::WorldGenContext;

use super::{WorldCarver, can_reach, carve_ellipsoid};

const fn default_start_vertical_radius_multiplier() -> FloatProvider {
    FloatProvider::Constant(1.0)
}

#[derive(Debug, Clone, Deserialize)]
pub struct CaveWorldCarver {
    pub probability: f32,
    pub y: HeightProvider,
    pub count: IntProvider,
    pub thickness: FloatProvider,
    #[serde(default)]
    pub weird_thickness_bias: bool,
    pub room_vertical_radius_multiplier: FloatProvider,
    pub horizontal_radius_multiplier: FloatProvider,
    pub vertical_radius_multiplier: FloatProvider,
    #[serde(default = "default_start_vertical_radius_multiplier")]
    pub start_vertical_radius_multiplier: FloatProvider,
    pub floor_level: FloatProvider,
}

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
    }
}

impl CaveWorldCarver {
    pub fn carve(
        &self,
        context: &WorldGenContext,
        random: &mut RandomSource,
        chunk_pos: ChunkPos,
        source_chunk_pos: ChunkPos,
        output: &mut CarvingMask,
    ) -> bool {
        let max_distance = (WorldCarver::get_range() * 2 - 1) << 4;
        let cave_count = self.count.sample(random);
        for _ in 0..cave_count {
            let x = f64::from(source_chunk_pos.0.x * 16 + random.next_i32_bounded(16));
            let y = f64::from(self.y.sample(random, context));
            let z = f64::from(source_chunk_pos.0.y * 16 + random.next_i32_bounded(16));
            let horizontal_radius_multiplier =
                f64::from(self.horizontal_radius_multiplier.sample(random));
            let vertical_radius_multiplier =
                f64::from(self.vertical_radius_multiplier.sample(random));
            let start_vertical_radius_multiplier =
                f64::from(self.start_vertical_radius_multiplier.sample(random));
            let floor_level = f64::from(self.floor_level.sample(random));
            let skip_checker = |xd, yd, zd, _world_y| should_skip(xd, yd, zd, floor_level);
            let mut tunnels = 1;
            if random.next_i32_bounded(4) == 0 {
                let y_scale = f64::from(self.room_vertical_radius_multiplier.sample(random));
                let thickness = 1.0 + random.next_f32() * 6.0;
                Self::create_room(
                    chunk_pos,
                    x,
                    y,
                    z,
                    thickness,
                    y_scale,
                    output,
                    &skip_checker,
                );
                tunnels += random.next_i32_bounded(4);
            }

            for _ in 0..tunnels {
                let horizontal_rotation = random.next_f32() * (PI * 2.0) as f32;
                let vertical_rotation = (random.next_f32() - 0.5) / 4.0;
                let thickness = self.get_thickness(random);
                let distance = max_distance - random.next_i32_bounded(max_distance / 4);
                let tunnel_seed = random.next_i64();
                Self::create_tunnel(
                    chunk_pos,
                    tunnel_seed,
                    x,
                    y,
                    z,
                    horizontal_radius_multiplier,
                    vertical_radius_multiplier,
                    thickness,
                    horizontal_rotation,
                    vertical_rotation,
                    0,
                    distance,
                    start_vertical_radius_multiplier,
                    output,
                    &skip_checker,
                );
            }
        }
        true
    }

    fn get_thickness(&self, random: &mut RandomSource) -> f32 {
        let mut thickness = self.thickness.sample(random);
        if self.weird_thickness_bias && random.next_i32_bounded(10) == 0 {
            thickness *= random.next_f32() * random.next_f32() * 3.0 + 1.0;
        }
        thickness
    }

    #[expect(
        clippy::too_many_arguments,
        reason = "mirrors CaveWorldCarver.createRoom"
    )]
    fn create_room(
        chunk_pos: ChunkPos,
        x: f64,
        y: f64,
        z: f64,
        thickness: f32,
        y_scale: f64,
        output: &mut CarvingMask,
        skip_checker: &dyn Fn(f64, f64, f64, i32) -> bool,
    ) {
        let horizontal_radius = 1.5 + f64::from(sin(f64::from((PI / 2.0) as f32)) * thickness);
        let vertical_radius = horizontal_radius * y_scale;
        carve_ellipsoid(
            chunk_pos,
            x + 1.0,
            y,
            z,
            horizontal_radius,
            vertical_radius,
            output,
            skip_checker,
        );
    }

    #[expect(
        clippy::too_many_arguments,
        reason = "mirrors CaveWorldCarver.createTunnel"
    )]
    fn create_tunnel(
        chunk_pos: ChunkPos,
        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,
        output: &mut CarvingMask,
        skip_checker: &dyn Fn(f64, f64, f64, i32) -> bool,
    ) {
        let mut random = RandomSource::Legacy(LegacyRandomSource::from_seed(tunnel_seed as u64));
        let split_point = random.next_i32_bounded(dist / 2) + dist / 4;
        let steep = random.next_i32_bounded(6) == 0;
        let mut y_rota = 0.0_f32;
        let mut x_rota = 0.0_f32;

        for current_step in step..dist {
            let horizontal_radius = 1.5
                + f64::from(
                    sin(f64::from(PI as f32 * current_step as f32 / dist as f32)) * thickness,
                );
            let vertical_radius = horizontal_radius * y_scale;
            let cos_x = cos(f64::from(vertical_rotation));
            x += f64::from(cos(f64::from(horizontal_rotation)) * cos_x);
            y += f64::from(sin(f64::from(vertical_rotation)));
            z += f64::from(sin(f64::from(horizontal_rotation)) * cos_x);
            vertical_rotation *= if 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 {
                let seed = random.next_i64();
                let branch_thickness = random.next_f32() * 0.5 + 0.5;
                Self::create_tunnel(
                    chunk_pos,
                    seed,
                    x,
                    y,
                    z,
                    horizontal_radius_multiplier,
                    vertical_radius_multiplier,
                    branch_thickness,
                    horizontal_rotation - (PI / 2.0) as f32,
                    vertical_rotation / 3.0,
                    current_step,
                    dist,
                    1.0,
                    output,
                    skip_checker,
                );
                let seed = random.next_i64();
                let branch_thickness = random.next_f32() * 0.5 + 0.5;
                Self::create_tunnel(
                    chunk_pos,
                    seed,
                    x,
                    y,
                    z,
                    horizontal_radius_multiplier,
                    vertical_radius_multiplier,
                    branch_thickness,
                    horizontal_rotation + (PI / 2.0) as f32,
                    vertical_rotation / 3.0,
                    current_step,
                    dist,
                    1.0,
                    output,
                    skip_checker,
                );
                return;
            }

            if random.next_i32_bounded(4) != 0 {
                if !can_reach(chunk_pos, x, z, current_step, dist, thickness) {
                    return;
                }
                carve_ellipsoid(
                    chunk_pos,
                    x,
                    y,
                    z,
                    horizontal_radius * horizontal_radius_multiplier,
                    vertical_radius * vertical_radius_multiplier,
                    output,
                    skip_checker,
                );
            }
        }
    }
}