chunkgen 0.2.0

Minecraft world generation library
Documentation
pub mod canyon;
pub mod cave;

use serde::Deserialize;

use crate::util::pos::ChunkPos;
use crate::util::rng::RandomSource;
use crate::world::chunk::carving_mask::CarvingMask;
use crate::world::worldgen::worldgen_context::WorldGenContext;

use canyon::CanyonWorldCarver;
use cave::CaveWorldCarver;

#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type")]
pub enum WorldCarver {
    #[serde(rename = "minecraft:cave")]
    Cave(CaveWorldCarver),
    #[serde(rename = "minecraft:canyon")]
    Canyon(CanyonWorldCarver),
}

const fn middle_block_x(chunk_pos: ChunkPos) -> i32 {
    chunk_pos.0.x * 16 + 8
}

const fn middle_block_z(chunk_pos: ChunkPos) -> i32 {
    chunk_pos.0.y * 16 + 8
}

impl WorldCarver {
    pub const fn get_range() -> i32 {
        4
    }

    pub fn is_start_chunk(&self, random: &mut RandomSource) -> bool {
        let probability = match self {
            Self::Cave(cave) => cave.probability,
            Self::Canyon(canyon) => canyon.probability,
        };
        random.next_f32() <= probability
    }

    pub fn carve(
        &self,
        context: &WorldGenContext,
        random: &mut RandomSource,
        chunk_pos: ChunkPos,
        source_chunk_pos: ChunkPos,
        output: &mut CarvingMask,
    ) -> bool {
        match self {
            Self::Cave(cave) => cave.carve(context, random, chunk_pos, source_chunk_pos, output),
            Self::Canyon(canyon) => {
                canyon.carve(context, random, chunk_pos, source_chunk_pos, output)
            }
        }
    }
}

#[expect(
    clippy::too_many_arguments,
    reason = "mirrors WorldCarver.carveEllipsoid"
)]
pub fn carve_ellipsoid(
    chunk_pos: ChunkPos,
    x: f64,
    y: f64,
    z: f64,
    horizontal_radius: f64,
    vertical_radius: f64,
    output: &mut CarvingMask,
    skip_checker: &dyn Fn(f64, f64, f64, i32) -> bool,
) {
    let center_x = f64::from(middle_block_x(chunk_pos));
    let center_z = f64::from(middle_block_z(chunk_pos));
    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_pos.0.x * 16;
    let chunk_min_z = chunk_pos.0.y * 16;
    let min_x_index = ((x - horizontal_radius).floor() as i32 - chunk_min_x - 1).max(0);
    let max_x_index = ((x + horizontal_radius).floor() as i32 - chunk_min_x).min(15);
    let min_y = ((y - vertical_radius).floor() as i32 - 1).max(output.min_y());
    let max_y = ((y + vertical_radius).floor() as i32 + 1).min(output.max_y());
    let min_z_index = ((z - horizontal_radius).floor() as i32 - chunk_min_z - 1).max(0);
    let max_z_index = ((z + horizontal_radius).floor() as i32 - chunk_min_z).min(15);
    for x_index in min_x_index..=max_x_index {
        let world_x = chunk_min_x + x_index;
        let xd = (f64::from(world_x) + 0.5 - x) / horizontal_radius;
        for z_index in min_z_index..=max_z_index {
            let world_z = chunk_min_z + z_index;
            let zd = (f64::from(world_z) + 0.5 - z) / horizontal_radius;
            if xd * xd + zd * zd >= 1.0 {
                continue;
            }
            let mut world_y = max_y;
            while world_y > min_y {
                let yd = (f64::from(world_y) - 0.5 - y) / vertical_radius;
                if !skip_checker(xd, yd, zd, world_y) {
                    output.carve(x_index, world_y, z_index);
                }
                world_y -= 1;
            }
        }
    }
}

pub fn can_reach(
    chunk_pos: ChunkPos,
    x: f64,
    z: f64,
    current_step: i32,
    total_steps: i32,
    thickness: f32,
) -> bool {
    let x_mid = f64::from(middle_block_x(chunk_pos));
    let z_mid = f64::from(middle_block_z(chunk_pos));
    let xd = x - x_mid;
    let zd = z - z_mid;
    let remaining = f64::from(total_steps - current_step);
    let rr = f64::from(thickness + 2.0 + 16.0);
    xd * xd + zd * zd - remaining * remaining <= rr * rr
}