Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
Download file

root / crates / common / src / noise.rs

//! Ports of `NoiseGeneratorPerlin`, `NoiseGeneratorOctaves`, `NoiseGenerator2` and
//! `NoiseGeneratorOctaves2`, including their quirks.

use crate::JavaRandom;

fn shuffled_permutations(random: &mut JavaRandom) -> [u8; 512] {
    let mut permutations = std::array::from_fn(|i| i as u8);
    for i in 0..256 {
        let j = (random.next_int(256 - i as i32) + i as i32) as usize;
        permutations.swap(i, j);
        permutations[i + 256] = permutations[i];
    }
    permutations
}

fn fade(t: f64) -> f64 {
    t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
}

fn lerp(t: f64, a: f64, b: f64) -> f64 {
    a + t * (b - a)
}

fn grad(hash: u8, x: f64, y: f64, z: f64) -> f64 {
    let hash = hash & 15;
    let u = if hash < 8 { x } else { y };
    let v = match hash {
        0..4 => y,
        12 | 14 => x,
        _ => z,
    };
    (if hash & 1 == 0 { u } else { -u }) + (if hash & 2 == 0 { v } else { -v })
}

fn grad_2d(hash: u8, x: f64, z: f64) -> f64 {
    let hash = hash & 15;
    let u = f64::from(1 - i32::from((hash & 8) >> 3)) * x;
    let v = match hash {
        0..4 => 0.0,
        12 | 14 => x,
        _ => z,
    };
    (if hash & 1 == 0 { u } else { -u }) + (if hash & 2 == 0 { v } else { -v })
}

/// Splits a coordinate into its 256-periodic lattice cell and the fractional part.
fn lattice(coordinate: f64) -> (usize, f64) {
    let mut cell = coordinate as i32;
    if coordinate < f64::from(cell) {
        cell = cell.wrapping_sub(1);
    }
    ((cell & 255) as usize, coordinate - f64::from(cell))
}

pub struct Perlin {
    permutations: [u8; 512],
    offset: [f64; 3],
}

impl Perlin {
    pub fn new(random: &mut JavaRandom) -> Self {
        let offset = std::array::from_fn(|_| random.next_double() * 256.0);
        Self {
            permutations: shuffled_permutations(random),
            offset,
        }
    }

    fn p(&self, index: usize) -> usize {
        usize::from(self.permutations[index])
    }

    pub fn sample(&self, x: f64, y: f64, z: f64) -> f64 {
        let (xi, x) = lattice(x + self.offset[0]);
        let (yi, y) = lattice(y + self.offset[1]);
        let (zi, z) = lattice(z + self.offset[2]);
        let (v, w) = (fade(y), fade(z));
        let c = self.corners([xi, yi, zi], [x, y, z], fade(x));
        lerp(w, lerp(v, c[0], c[1]), lerp(v, c[2], c[3]))
    }

    /// The four edges along y of the lattice cube, interpolated along x by `u`.
    fn corners(&self, [xi, yi, zi]: [usize; 3], [x, y, z]: [f64; 3], u: f64) -> [f64; 4] {
        let a = self.p(xi) + yi;
        let aa = self.p(a) + zi;
        let ab = self.p(a + 1) + zi;
        let b = self.p(xi + 1) + yi;
        let ba = self.p(b) + zi;
        let bb = self.p(b + 1) + zi;
        let g = |i: usize, dx: f64, dy: f64, dz: f64| grad(self.permutations[i], dx, dy, dz);
        [
            lerp(u, g(aa, x, y, z), g(ba, x - 1.0, y, z)),
            lerp(u, g(ab, x, y - 1.0, z), g(bb, x - 1.0, y - 1.0, z)),
            lerp(u, g(aa + 1, x, y, z - 1.0), g(ba + 1, x - 1.0, y, z - 1.0)),
            lerp(
                u,
                g(ab + 1, x, y - 1.0, z - 1.0),
                g(bb + 1, x - 1.0, y - 1.0, z - 1.0),
            ),
        ]
    }

    /// Adds `noise / amplitude` for a `width × height × depth` grid, laid out x-major, then z, then y.
    fn add_to(
        &self,
        out: &mut [f64],
        origin: [f64; 3],
        size: [usize; 3],
        scale: [f64; 3],
        amplitude: f64,
    ) {
        let [width, height, depth] = size;
        let inverse_amplitude = 1.0 / amplitude;
        let mut index = 0;

        if height == 1 {
            for ix in 0..width {
                let (xi, x) = lattice((origin[0] + ix as f64) * scale[0] + self.offset[0]);
                let u = fade(x);
                for iz in 0..depth {
                    let (zi, z) = lattice((origin[2] + iz as f64) * scale[2] + self.offset[2]);
                    let w = fade(z);
                    let a = self.p(self.p(xi)) + zi;
                    let b = self.p(self.p(xi + 1)) + zi;
                    let near = lerp(
                        u,
                        grad_2d(self.permutations[a], x, z),
                        grad(self.permutations[b], x - 1.0, 0.0, z),
                    );
                    let far = lerp(
                        u,
                        grad(self.permutations[a + 1], x, 0.0, z - 1.0),
                        grad(self.permutations[b + 1], x - 1.0, 0.0, z - 1.0),
                    );
                    out[index] += lerp(w, near, far) * inverse_amplitude;
                    index += 1;
                }
            }
            return;
        }

        let mut cached_yi = usize::MAX;
        let mut corners = [0.0; 4];
        for ix in 0..width {
            let (xi, x) = lattice((origin[0] + ix as f64) * scale[0] + self.offset[0]);
            let u = fade(x);
            for iz in 0..depth {
                let (zi, z) = lattice((origin[2] + iz as f64) * scale[2] + self.offset[2]);
                let w = fade(z);
                for iy in 0..height {
                    let (yi, y) = lattice((origin[1] + iy as f64) * scale[1] + self.offset[1]);
                    let v = fade(y);
                    // The original only refreshes gradients when the y cell changes,
                    // reusing the stale y fraction otherwise.
                    if iy == 0 || yi != cached_yi {
                        cached_yi = yi;
                        corners = self.corners([xi, yi, zi], [x, y, z], u);
                    }
                    let near = lerp(v, corners[0], corners[1]);
                    let far = lerp(v, corners[2], corners[3]);
                    out[index] += lerp(w, near, far) * inverse_amplitude;
                    index += 1;
                }
            }
        }
    }
}

pub struct PerlinOctaves {
    octaves: Box<[Perlin]>,
}

impl PerlinOctaves {
    pub fn new(random: &mut JavaRandom, count: usize) -> Self {
        Self {
            octaves: (0..count).map(|_| Perlin::new(random)).collect(),
        }
    }

    pub fn sample_2d(&self, x: f64, z: f64) -> f64 {
        let mut total = 0.0;
        let mut frequency = 1.0;
        for octave in &self.octaves {
            total += octave.sample(x * frequency, z * frequency, 0.0) / frequency;
            frequency /= 2.0;
        }
        total
    }

    /// Fills `out` with a grid laid out x-major, then z, then y. A height of 1 uses the 2D path.
    pub fn fill(&self, out: &mut [f64], origin: [f64; 3], size: [usize; 3], scale: [f64; 3]) {
        let len = size.iter().product();
        out[..len].fill(0.0);
        let mut amplitude = 1.0;
        for octave in &self.octaves {
            let scale = scale.map(|s| s * amplitude);
            octave.add_to(&mut out[..len], origin, size, scale, amplitude);
            amplitude /= 2.0;
        }
    }

    /// The `(x, z)` overload of `generateNoiseOctaves`.
    pub fn fill_2d(&self, out: &mut [f64], x: i32, z: i32, size: [usize; 2], scale: [f64; 2]) {
        self.fill(
            out,
            [f64::from(x), 10.0, f64::from(z)],
            [size[0], 1, size[1]],
            [scale[0], 1.0, scale[1]],
        );
    }
}

const GRADIENTS_2D: [[f64; 2]; 12] = [
    [1.0, 1.0],
    [-1.0, 1.0],
    [1.0, -1.0],
    [-1.0, -1.0],
    [1.0, 0.0],
    [-1.0, 0.0],
    [1.0, 0.0],
    [-1.0, 0.0],
    [0.0, 1.0],
    [0.0, -1.0],
    [0.0, 1.0],
    [0.0, -1.0],
];

pub struct Simplex {
    permutations: [u8; 512],
    offset: [f64; 2],
}

impl Simplex {
    const SKEW: f64 = 0.366_025_403_784_438_6;
    const UNSKEW: f64 = 0.211_324_865_405_187_13;

    pub fn new(random: &mut JavaRandom) -> Self {
        let [x, z, _unused] = std::array::from_fn(|_| random.next_double() * 256.0);
        Self {
            permutations: shuffled_permutations(random),
            offset: [x, z],
        }
    }

    fn p(&self, index: usize) -> usize {
        usize::from(self.permutations[index])
    }

    fn corner(gradient: usize, x: f64, z: f64) -> f64 {
        let t = 0.5 - x * x - z * z;
        if t < 0.0 {
            return 0.0;
        }
        let t = t * t;
        let [gx, gz] = GRADIENTS_2D[gradient];
        t * t * (gx * x + gz * z)
    }

    fn floor(value: f64) -> i32 {
        if value > 0.0 {
            value as i32
        } else {
            value as i32 - 1
        }
    }

    fn add_to(
        &self,
        out: &mut [f64],
        origin: [f64; 2],
        size: [usize; 2],
        scale: [f64; 2],
        amplitude: f64,
    ) {
        let mut index = 0;
        for ix in 0..size[0] {
            let x = (origin[0] + ix as f64) * scale[0] + self.offset[0];
            for iz in 0..size[1] {
                let z = (origin[1] + iz as f64) * scale[1] + self.offset[1];
                let skew = (x + z) * Self::SKEW;
                let i = Self::floor(x + skew);
                let j = Self::floor(z + skew);
                let unskew = f64::from(i + j) * Self::UNSKEW;
                let x0 = x - (f64::from(i) - unskew);
                let z0 = z - (f64::from(j) - unskew);
                let (i1, j1) = if x0 > z0 { (1, 0) } else { (0, 1) };
                let x1 = x0 - i1 as f64 + Self::UNSKEW;
                let z1 = z0 - j1 as f64 + Self::UNSKEW;
                let x2 = x0 - 1.0 + 2.0 * Self::UNSKEW;
                let z2 = z0 - 1.0 + 2.0 * Self::UNSKEW;
                let ii = (i & 255) as usize;
                let jj = (j & 255) as usize;
                let g0 = self.p(ii + self.p(jj)) % 12;
                let g1 = self.p(ii + i1 + self.p(jj + j1)) % 12;
                let g2 = self.p(ii + 1 + self.p(jj + 1)) % 12;
                let n =
                    Self::corner(g0, x0, z0) + Self::corner(g1, x1, z1) + Self::corner(g2, x2, z2);
                out[index] += 70.0 * n * amplitude;
                index += 1;
            }
        }
    }
}

pub struct SimplexOctaves {
    octaves: Box<[Simplex]>,
}

impl SimplexOctaves {
    pub fn new(random: &mut JavaRandom, count: usize) -> Self {
        Self {
            octaves: (0..count).map(|_| Simplex::new(random)).collect(),
        }
    }

    /// A `width × depth` grid laid out x-major.
    pub fn sample(
        &self,
        origin: [f64; 2],
        size: [usize; 2],
        scale: [f64; 2],
        lacunarity: f64,
        persistence: f64,
    ) -> Vec<f64> {
        let mut out = vec![0.0; size[0] * size[1]];
        let scale = scale.map(|s| s / 1.5);
        let (mut frequency, mut amplitude) = (1.0, 1.0);
        for octave in &self.octaves {
            let scale = scale.map(|s| s * frequency);
            octave.add_to(&mut out, origin, size, scale, 0.55 / amplitude);
            frequency *= lacunarity;
            amplitude *= persistence;
        }
        out
    }
}