chunkgen 0.2.0

Minecraft world generation library
Documentation
use std::sync::Arc;

use rustc_hash::{FxHashMap, FxHashSet};
use shulkr::util::BlockPosition;
use shulkr::world::block::BlockState;

use crate::registry::biome::{BiomeRef, cold_enough_to_snow};
use crate::util::rng::RandomFactory;
use crate::util::value_providers::VerticalAnchor;
use crate::world::worldgen::densityfunction::context::SamplerContext;
use crate::world::worldgen::densityfunction::eval::{sample_value, sample_volume};
use crate::world::worldgen::densityfunction::function::Df;
use crate::world::worldgen::densityfunction::sampler::S;
use crate::world::worldgen::densityfunction::volume::DensityVolume;
use crate::world::worldgen::random_state::RandomState;
use crate::world::worldgen::synth::Noise;
use crate::world::worldgen::worldgen_context::WorldGenContext;

use super::MaterialSystem;

const HOW_FAR_BELOW_PRELIMINARY_SURFACE_LEVEL_TO_BUILD_SURFACE: i32 = 8;

struct NoiseSampler {
    noise: Arc<Noise>,
    is_3d: bool,
    last_update: i64,
    last_noise: f64,
}

pub struct DensityGetter {
    sampler: S,
    buffer: Option<Vec<f32>>,
}

pub struct MaterialRuleContext<'a, 'b> {
    system: &'a MaterialSystem,
    random_state: &'a RandomState,
    expected_volume: DensityVolume,
    density_samplers: &'a mut SamplerContext<'b>,
    biome_getter: &'a dyn Fn(BlockPosition) -> BiomeRef,
    context: WorldGenContext,
    possible_biomes: Option<&'a FxHashSet<u16>>,
    preliminary_surface_volume: DensityVolume,
    preliminary_surface_buffer: Option<Vec<f32>>,
    noise_sampler_index: FxHashMap<(String, bool), usize>,
    noise_samplers: Vec<NoiseSampler>,
    last_update_xz: i64,
    block_x: i32,
    block_z: i32,
    surface_gradient_x: i32,
    surface_gradient_z: i32,
    surface_depth: i32,
    last_surface_depth2_update: i64,
    surface_secondary: f64,
    last_min_surface_level_update: i64,
    min_surface_level: i32,
    last_update_y: i64,
    biome: Option<BiomeRef>,
    block_y: i32,
    water_height: i32,
    stone_depth_below: i32,
    stone_depth_above: i32,
}

impl<'a, 'b> MaterialRuleContext<'a, 'b> {
    pub fn new(
        system: &'a MaterialSystem,
        random_state: &'a RandomState,
        expected_volume: DensityVolume,
        density_samplers: &'a mut SamplerContext<'b>,
        biome_getter: &'a dyn Fn(BlockPosition) -> BiomeRef,
        context: WorldGenContext,
        possible_biomes: Option<&'a FxHashSet<u16>>,
    ) -> Self {
        let last_update_xz = i64::MIN + 1;
        Self {
            system,
            random_state,
            expected_volume,
            density_samplers,
            biome_getter,
            context,
            possible_biomes,
            preliminary_surface_volume: DensityVolume::new(
                [expected_volume.size_x, 1, expected_volume.size_z],
                [expected_volume.min_block_x, 0, expected_volume.min_block_z],
            ),
            preliminary_surface_buffer: None,
            noise_sampler_index: FxHashMap::default(),
            noise_samplers: Vec::new(),
            last_update_xz,
            block_x: 0,
            block_z: 0,
            surface_gradient_x: 0,
            surface_gradient_z: 0,
            surface_depth: 0,
            last_surface_depth2_update: last_update_xz - 1,
            surface_secondary: 0.0,
            last_min_surface_level_update: last_update_xz - 1,
            min_surface_level: 0,
            last_update_y: i64::MIN + 1,
            biome: None,
            block_y: 0,
            water_height: 0,
            stone_depth_below: 0,
            stone_depth_above: 0,
        }
    }

    pub fn update_xz(
        &mut self,
        block_x: i32,
        block_z: i32,
        surface_gradient_x: i32,
        surface_gradient_z: i32,
    ) {
        self.last_update_xz += 1;
        self.last_update_y += 1;
        self.block_x = block_x;
        self.block_z = block_z;
        self.surface_gradient_x = surface_gradient_x;
        self.surface_gradient_z = surface_gradient_z;
        self.surface_depth = self.system.get_surface_depth(block_x, block_z);
    }

    pub const fn update_y(
        &mut self,
        stone_depth_above: i32,
        stone_depth_below: i32,
        water_height: i32,
        block_y: i32,
    ) {
        self.last_update_y += 1;
        self.biome = None;
        self.block_y = block_y;
        self.water_height = water_height;
        self.stone_depth_below = stone_depth_below;
        self.stone_depth_above = stone_depth_above;
    }

    pub const fn last_update_xz(&self) -> i64 {
        self.last_update_xz
    }

    pub const fn last_update_y(&self) -> i64 {
        self.last_update_y
    }

    pub fn get_surface_secondary(&mut self) -> f64 {
        if self.last_surface_depth2_update != self.last_update_xz {
            self.last_surface_depth2_update = self.last_update_xz;
            self.surface_secondary = self
                .system
                .get_surface_secondary(self.block_x, self.block_z);
        }
        self.surface_secondary
    }

    pub fn get_biome(&mut self) -> BiomeRef {
        if let Some(biome) = self.biome {
            return biome;
        }
        let biome = (self.biome_getter)(self.block_pos());
        self.biome = Some(biome);
        biome
    }

    pub const fn get_sea_level(&self) -> i32 {
        self.system.get_sea_level()
    }

    pub fn cold_enough_to_snow(&mut self) -> bool {
        let biome = self.get_biome();
        cold_enough_to_snow(biome.value(), self.block_pos(), self.get_sea_level())
    }

    pub fn get_min_surface_level(&mut self) -> i32 {
        if self.last_min_surface_level_update != self.last_update_xz {
            self.last_min_surface_level_update = self.last_update_xz;
            let sampler = self
                .random_state
                .get_sampler(self.system.preliminary_surface_function());
            let preliminary_surface_level =
                match self
                    .preliminary_surface_volume
                    .index_of_block(self.block_x, 0, self.block_z)
                {
                    Some(index) => {
                        if self.preliminary_surface_buffer.is_none() {
                            let mut buffer = vec![0.0f32; self.preliminary_surface_volume.size()];
                            sample_volume(
                                &sampler,
                                self.density_samplers,
                                &mut buffer,
                                &self.preliminary_surface_volume,
                            );
                            self.preliminary_surface_buffer = Some(buffer);
                        }
                        self.preliminary_surface_buffer
                            .as_ref()
                            .map_or(0.0, |b| b[index])
                    }
                    None => sample_value(
                        &sampler,
                        self.density_samplers,
                        self.block_x,
                        0,
                        self.block_z,
                    ),
                };
            self.min_surface_level = preliminary_surface_level.floor() as i32 + self.surface_depth
                - HOW_FAR_BELOW_PRELIMINARY_SURFACE_LEVEL_TO_BUILD_SURFACE;
        }
        self.min_surface_level
    }

    pub fn get_noise_sampler(&mut self, noise: &str, is_3d: bool) -> usize {
        if let Some(index) = self.noise_sampler_index.get(&(noise.to_owned(), is_3d)) {
            return *index;
        }
        let index = self.noise_samplers.len();
        let last_update = if is_3d {
            self.last_update_y - 1
        } else {
            self.last_update_xz - 1
        };
        self.noise_samplers.push(NoiseSampler {
            noise: self.random_state.get_or_create_noise(noise),
            is_3d,
            last_update,
            last_noise: 0.0,
        });
        self.noise_sampler_index
            .insert((noise.to_owned(), is_3d), index);
        index
    }

    pub fn sample_noise(&mut self, index: usize) -> f64 {
        let (last_update_xz, last_update_y) = (self.last_update_xz, self.last_update_y);
        let (block_x, block_y, block_z) = (self.block_x, self.block_y, self.block_z);
        let sampler = &mut self.noise_samplers[index];
        let current = if sampler.is_3d {
            last_update_y
        } else {
            last_update_xz
        };
        if sampler.last_update != current {
            sampler.last_noise = if sampler.is_3d {
                f64::from(sampler.noise.get(
                    f64::from(block_x),
                    f64::from(block_y),
                    f64::from(block_z),
                ))
            } else {
                f64::from(
                    sampler
                        .noise
                        .get(f64::from(block_x), 0.0, f64::from(block_z)),
                )
            };
            sampler.last_update = current;
        }
        sampler.last_noise
    }

    pub fn get_or_create_random_factory(&self, name: &str) -> RandomFactory {
        self.random_state.get_or_create_random_factory(name)
    }

    pub fn get_densities_in_chunk(&mut self, function: &Df, prefill: bool) -> DensityGetter {
        let sampler = self.random_state.get_sampler(function);
        let buffer = prefill.then(|| {
            let mut buffer = vec![0.0f32; self.expected_volume.size()];
            sample_volume(
                &sampler,
                self.density_samplers,
                &mut buffer,
                &self.expected_volume,
            );
            buffer
        });
        DensityGetter { sampler, buffer }
    }

    pub fn get_density(&mut self, getter: &DensityGetter) -> f32 {
        if let Some(buffer) = &getter.buffer
            && let Some(index) =
                self.expected_volume
                    .index_of_block(self.block_x, self.block_y, self.block_z)
        {
            return buffer[index];
        }
        sample_value(
            &getter.sampler,
            self.density_samplers,
            self.block_x,
            self.block_y,
            self.block_z,
        )
    }

    pub const fn possible_biomes(&self) -> Option<&FxHashSet<u16>> {
        self.possible_biomes
    }

    pub const fn stone_depth_above(&self) -> i32 {
        self.stone_depth_above
    }

    pub const fn stone_depth_below(&self) -> i32 {
        self.stone_depth_below
    }

    pub const fn surface_depth(&self) -> i32 {
        self.surface_depth
    }

    pub const fn water_height(&self) -> i32 {
        self.water_height
    }

    pub const fn block_x(&self) -> i32 {
        self.block_x
    }

    pub const fn block_y(&self) -> i32 {
        self.block_y
    }

    pub const fn block_z(&self) -> i32 {
        self.block_z
    }

    pub const fn block_pos(&self) -> BlockPosition {
        BlockPosition::new(self.block_x, self.block_y, self.block_z)
    }

    pub const fn surface_gradient_x(&self) -> i32 {
        self.surface_gradient_x
    }

    pub const fn surface_gradient_z(&self) -> i32 {
        self.surface_gradient_z
    }

    pub const fn resolve_anchor_y(&self, anchor: VerticalAnchor) -> i32 {
        anchor.resolve_y(&self.context)
    }

    pub fn get_band(&self, x: i32, y: i32, z: i32) -> BlockState {
        self.system.get_band(x, y, z)
    }
}