chunkgen 0.2.0

Minecraft world generation library
Documentation
use crate::util::rng::RandomSource;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Gradient {
    pub x: i32,
    pub y: i32,
    pub z: i32,
}

impl Gradient {
    const fn new(x: i32, y: i32, z: i32) -> Self {
        Self { x, y, z }
    }

    pub fn dot_f64(self, x: f64, y: f64, z: f64) -> f64 {
        f64::from(self.x) * x + f64::from(self.y) * y + f64::from(self.z) * z
    }

    pub fn dot(self, x: f32, y: f32, z: f32) -> f32 {
        self.x as f32 * x + self.y as f32 * y + self.z as f32 * z
    }

    pub fn dot_xz(self, x: f32, z: f32) -> f32 {
        self.x as f32 * x + self.z as f32 * z
    }
}

pub const GRADIENT: [Gradient; 16] = [
    Gradient::new(1, 1, 0),
    Gradient::new(-1, 1, 0),
    Gradient::new(1, -1, 0),
    Gradient::new(-1, -1, 0),
    Gradient::new(1, 0, 1),
    Gradient::new(-1, 0, 1),
    Gradient::new(1, 0, -1),
    Gradient::new(-1, 0, -1),
    Gradient::new(0, 1, 1),
    Gradient::new(0, -1, 1),
    Gradient::new(0, 1, -1),
    Gradient::new(0, -1, -1),
    Gradient::new(1, 1, 0),
    Gradient::new(0, -1, 1),
    Gradient::new(-1, 1, 0),
    Gradient::new(0, -1, -1),
];

const ROUND_OFF: f64 = 33_554_432.0;
const HALF_ROUND_OFF: f64 = f64::from_bits(16_777_216.0_f64.to_bits() - 1);

pub fn wrap(x: f64) -> f64 {
    if (-HALF_ROUND_OFF..HALF_ROUND_OFF).contains(&x) {
        x
    } else {
        x - (x / ROUND_OFF + 0.5).floor() * ROUND_OFF
    }
}

pub const fn floor(v: f64) -> i32 {
    v.floor() as i32
}

pub fn smoothstep(x: f32) -> f32 {
    x * x * x * (x * (x * 6.0 - 15.0) + 10.0)
}

pub fn lerp(alpha: f32, p0: f32, p1: f32) -> f32 {
    p0 + alpha * (p1 - p0)
}

pub fn lerp2(a1: f32, a2: f32, x00: f32, x10: f32, x01: f32, x11: f32) -> f32 {
    lerp(a2, lerp(a1, x00, x10), lerp(a1, x01, x11))
}

#[expect(clippy::too_many_arguments, reason = "mirrors Mth.lerp3")]
pub fn lerp3(
    a1: f32,
    a2: f32,
    a3: f32,
    x000: f32,
    x100: f32,
    x010: f32,
    x110: f32,
    x001: f32,
    x101: f32,
    x011: f32,
    x111: f32,
) -> f32 {
    lerp(
        a3,
        lerp2(a1, a2, x000, x100, x010, x110),
        lerp2(a1, a2, x001, x101, x011, x111),
    )
}

#[derive(Debug, Clone)]
pub struct GradientNoise {
    pub perms: [u8; 256],
    pub offset_x: f64,
    pub offset_y: f64,
    pub offset_z: f64,
}

impl GradientNoise {
    pub fn new(random: &mut RandomSource, noise_offset_scale: f64) -> Self {
        let offset_x = random.next_f64() * noise_offset_scale;
        let offset_y = random.next_f64() * noise_offset_scale;
        let offset_z = random.next_f64() * noise_offset_scale;
        let mut perms = [0u8; 256];
        for (i, p) in perms.iter_mut().enumerate() {
            *p = i as u8;
        }
        for i in 0..256 {
            let offset = random.next_i32_bounded((256 - i) as i32) as usize;
            perms.swap(i, offset + i);
        }
        Self {
            perms,
            offset_x,
            offset_y,
            offset_z,
        }
    }

    pub const fn permute(&self, x: i32) -> i32 {
        self.perms[(x & 0xFF) as usize] as i32
    }

    pub const fn permute_to_grad(&self, x: i32) -> Gradient {
        GRADIENT[(self.permute(x) & 15) as usize]
    }
}

pub const fn grad_dot(hash: i32, x: f32, y: f32, z: f32) -> f32 {
    let g = GRADIENT[(hash & 15) as usize];
    g.x as f32 * x + g.y as f32 * y + g.z as f32 * z
}