chunkgen 0.2.0

Minecraft world generation library
Documentation
use crate::util::interval::Interval;
use crate::util::rng::RandomSource;
use crate::world::worldgen::densityfunction::volume::DensityVolume;

use super::gradient_noise::{GradientNoise, floor, grad_dot, lerp3, smoothstep, wrap};

pub const STANDARD_DEVIATION: f64 = 0.270_224_783_124_521_1;

#[derive(Debug, Clone)]
pub struct PerlinNoise {
    pub base: GradientNoise,
}

impl PerlinNoise {
    pub fn new(random: &mut RandomSource) -> Self {
        Self {
            base: GradientNoise::new(random, 256.0),
        }
    }

    pub fn range() -> Interval {
        Interval::of_symmetric(2.0)
    }

    pub fn get2(&self, x: f64, y: f64) -> f32 {
        self.get(wrap(x), 0.0, wrap(y))
    }

    pub fn get(&self, x: f64, y: f64, z: f64) -> f32 {
        let x = wrap(x) + self.base.offset_x;
        let y = wrap(y) + self.base.offset_y;
        let z = wrap(z) + self.base.offset_z;
        let floor_x = floor(x);
        let floor_y = floor(y);
        let floor_z = floor(z);
        let relative_x = (x - f64::from(floor_x)) as f32;
        let relative_y = (y - f64::from(floor_y)) as f32;
        let relative_z = (z - f64::from(floor_z)) as f32;
        self.sample_and_lerp(
            floor_x, floor_y, floor_z, relative_x, relative_y, relative_z, relative_y,
        )
    }

    #[expect(
        clippy::too_many_arguments,
        reason = "mirrors PerlinNoise.sampleAndLerp"
    )]
    pub fn sample_and_lerp(
        &self,
        x: i32,
        y: i32,
        z: i32,
        relative_x: f32,
        relative_y: f32,
        relative_z: f32,
        original_relative_y: f32,
    ) -> f32 {
        let p = &self.base;
        let x0 = p.permute(x);
        let x1 = p.permute(x.wrapping_add(1));
        let xy00 = p.permute(x0 + y);
        let xy01 = p.permute(x0 + y + 1);
        let xy10 = p.permute(x1 + y);
        let xy11 = p.permute(x1 + y + 1);
        let d000 = grad_dot(p.permute(xy00 + z), relative_x, relative_y, relative_z);
        let d100 = grad_dot(
            p.permute(xy10 + z),
            relative_x - 1.0,
            relative_y,
            relative_z,
        );
        let d010 = grad_dot(
            p.permute(xy01 + z),
            relative_x,
            relative_y - 1.0,
            relative_z,
        );
        let d110 = grad_dot(
            p.permute(xy11 + z),
            relative_x - 1.0,
            relative_y - 1.0,
            relative_z,
        );
        let d001 = grad_dot(
            p.permute(xy00 + z + 1),
            relative_x,
            relative_y,
            relative_z - 1.0,
        );
        let d101 = grad_dot(
            p.permute(xy10 + z + 1),
            relative_x - 1.0,
            relative_y,
            relative_z - 1.0,
        );
        let d011 = grad_dot(
            p.permute(xy01 + z + 1),
            relative_x,
            relative_y - 1.0,
            relative_z - 1.0,
        );
        let d111 = grad_dot(
            p.permute(xy11 + z + 1),
            relative_x - 1.0,
            relative_y - 1.0,
            relative_z - 1.0,
        );
        let x_alpha = smoothstep(relative_x);
        let y_alpha = smoothstep(original_relative_y);
        let z_alpha = smoothstep(relative_z);
        lerp3(
            x_alpha, y_alpha, z_alpha, d000, d100, d010, d110, d001, d101, d011, d111,
        )
    }

    pub fn add_to_volume(
        &self,
        buffer: &mut [f32],
        volume: &DensityVolume,
        xz_scale: f64,
        y_scale: f64,
        amplitude: f32,
    ) {
        add_to_volume(
            &self.base,
            buffer,
            volume,
            xz_scale,
            y_scale,
            amplitude,
            |_, rel| rel as f32,
        );
    }
}

pub(super) fn add_to_volume(
    p: &GradientNoise,
    buffer: &mut [f32],
    volume: &DensityVolume,
    xz_scale: f64,
    y_scale: f64,
    amplitude: f32,
    fudged_relative_y: impl Fn(f64, f64) -> f32,
) {
    let mut d = [0.0f32; 8];
    let mut g = [0.0f32; 8];
    let mut index = 0;

    for index_z in 0..volume.size_z {
        let z = wrap(f64::from(volume.block_z(index_z)) * xz_scale) + p.offset_z;
        let floor_z = floor(z);
        let relative_z = (z - f64::from(floor_z)) as f32;
        let alpha_z = smoothstep(relative_z);

        for index_x in 0..volume.size_x {
            let x = wrap(f64::from(volume.block_x(index_x)) * xz_scale) + p.offset_x;
            let floor_x = floor(x);
            let relative_x = (x - f64::from(floor_x)) as f32;
            let x0 = p.permute(floor_x);
            let x1 = p.permute(floor_x.wrapping_add(1));
            let alpha_x = smoothstep(relative_x);
            let mut last_floor_y = i32::MIN;

            for index_y in 0..volume.size_y {
                let original_y = f64::from(volume.block_y(index_y)) * y_scale;
                let y = wrap(original_y) + p.offset_y;
                let floor_y = floor(y);
                let relative_y_d = y - f64::from(floor_y);
                let alpha_y = smoothstep(relative_y_d as f32);
                if last_floor_y != floor_y {
                    let xy00 = p.permute(x0 + floor_y);
                    let xy01 = p.permute(x0 + floor_y + 1);
                    let xy10 = p.permute(x1 + floor_y);
                    let xy11 = p.permute(x1 + floor_y + 1);
                    let corners = [
                        (xy00 + floor_z, relative_x, relative_z),
                        (xy10 + floor_z, relative_x - 1.0, relative_z),
                        (xy01 + floor_z, relative_x, relative_z),
                        (xy11 + floor_z, relative_x - 1.0, relative_z),
                        (xy00 + floor_z + 1, relative_x, relative_z - 1.0),
                        (xy10 + floor_z + 1, relative_x - 1.0, relative_z - 1.0),
                        (xy01 + floor_z + 1, relative_x, relative_z - 1.0),
                        (xy11 + floor_z + 1, relative_x - 1.0, relative_z - 1.0),
                    ];
                    for (i, (hash, cx, cz)) in corners.into_iter().enumerate() {
                        let grad = p.permute_to_grad(hash);
                        d[i] = grad.dot_xz(cx, cz);
                        g[i] = grad.y as f32;
                    }
                    last_floor_y = floor_y;
                }

                let ry = fudged_relative_y(original_y, relative_y_d);
                let value = lerp3(
                    alpha_x,
                    alpha_y,
                    alpha_z,
                    d[0] + g[0] * ry,
                    d[1] + g[1] * ry,
                    d[2] + g[2] * (ry - 1.0),
                    d[3] + g[3] * (ry - 1.0),
                    d[4] + g[4] * ry,
                    d[5] + g[5] * ry,
                    d[6] + g[6] * (ry - 1.0),
                    d[7] + g[7] * (ry - 1.0),
                );
                buffer[index] += amplitude * value;
                index += 1;
            }
        }
    }
}