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 / climate.rs

use crate::JavaRandom;

use crate::biome::Biome;
use crate::noise::SimplexOctaves;

/// Port of `WorldChunkManager`: temperature, rainfall and biome sampling.
pub enum Climate {
    Overworld(Noise),
    /// `WorldChunkManagerHell`: hot, dry and all `Hell`.
    Hell,
}

pub struct Noise {
    temperature: SimplexOctaves,
    rainfall: SimplexOctaves,
    variation: SimplexOctaves,
}

/// Per-column climate for a rectangular area, laid out x-major.
#[derive(Clone, Default)]
pub struct ClimateMap {
    pub temperature: Vec<f64>,
    pub rainfall: Vec<f64>,
    pub biomes: Vec<Biome>,
}

const TEMPERATURE_SCALE: [f64; 2] = [0.025_f32 as f64; 2];
const RAINFALL_SCALE: [f64; 2] = [0.05_f32 as f64; 2];

impl Noise {
    /// The temperatures blended with the variation noise, and that variation.
    fn temperatures(&self, x: i32, z: i32, size: [usize; 2]) -> (Vec<f64>, Vec<f64>) {
        let Self {
            temperature,
            variation,
            ..
        } = self;
        let origin = [f64::from(x), f64::from(z)];
        let mut temperature = temperature.sample(origin, size, TEMPERATURE_SCALE, 0.25, 0.5);
        let mut variation = variation.sample(origin, size, [0.25; 2], 0.588_235_294_117_647_1, 0.5);
        for (t, v) in temperature.iter_mut().zip(&mut variation) {
            *v = *v * 1.1 + 0.5;
            let mixed = (*t * 0.15 + 0.7) * (1.0 - 0.01) + *v * 0.01;
            *t = (1.0 - (1.0 - mixed) * (1.0 - mixed)).clamp(0.0, 1.0);
        }
        (temperature, variation)
    }
}

impl Climate {
    pub fn new(seed: i64) -> Self {
        let octaves = |salt: i64, count| {
            SimplexOctaves::new(&mut JavaRandom::new(seed.wrapping_mul(salt)), count)
        };
        Self::Overworld(Noise {
            temperature: octaves(9871, 4),
            rainfall: octaves(39811, 4),
            variation: octaves(543_321, 2),
        })
    }

    /// Port of `loadBlockGeneratorData`.
    pub fn sample(&self, x: i32, z: i32, width: usize, depth: usize) -> ClimateMap {
        let len = width * depth;
        let Self::Overworld(noise @ Noise { rainfall, .. }) = self else {
            return ClimateMap {
                temperature: vec![1.0; len],
                rainfall: vec![0.0; len],
                biomes: vec![Biome::Hell; len],
            };
        };
        let (temperature, variation) = noise.temperatures(x, z, [width, depth]);
        let origin = [f64::from(x), f64::from(z)];
        let mut rainfall = rainfall.sample(origin, [width, depth], RAINFALL_SCALE, 1.0 / 3.0, 0.5);
        for (r, v) in rainfall.iter_mut().zip(&variation) {
            *r = ((*r * 0.15 + 0.5) * (1.0 - 0.002) + v * 0.002).clamp(0.0, 1.0);
        }
        let biomes = temperature
            .iter()
            .zip(&rainfall)
            .map(|(&t, &r)| Biome::lookup(t, r))
            .collect();
        ClimateMap {
            temperature,
            rainfall,
            biomes,
        }
    }

    /// Port of `getTemperature`: the untransformed noise value that drives sky color.
    pub fn raw_temperature(&self, x: i32, z: i32) -> f64 {
        let Self::Overworld(noise) = self else {
            return 1.0;
        };
        let origin = [f64::from(x), f64::from(z)];
        noise
            .temperature
            .sample(origin, [1, 1], TEMPERATURE_SCALE, 0.5, 0.5)[0]
    }

    /// Port of `getTemperatures`, used by snow placement during population.
    pub fn temperatures(&self, x: i32, z: i32, width: usize, depth: usize) -> Vec<f64> {
        match self {
            Self::Overworld(noise) => noise.temperatures(x, z, [width, depth]).0,
            Self::Hell => vec![1.0; width * depth],
        }
    }
}