alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH 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
root crates server src terrain.rs
use common::JavaRandom;
use common::climate::{Climate, ClimateMap};
use common::noise::PerlinOctaves;
use common::world::chunk::{self, HEIGHT, VOLUME};
use common::world::{Block, ChunkPos};
use crate::caves::Caves;
use crate::nether::NetherGenerator;
const SEA_LEVEL: usize = 64;
const SAMPLES_XZ: usize = 5;
const SAMPLES_Y: usize = 17;
const HORIZONTAL: f64 = 684.412;
/// The terrain of one dimension.
pub enum Generator {
Overworld(OverworldGenerator),
Nether(NetherGenerator),
}
impl Generator {
pub fn seed(&self) -> i64 {
match self {
Self::Overworld(generator) => generator.seed,
Self::Nether(generator) => generator.seed,
}
}
/// `WorldProvider.worldType`: 0 for the Overworld, -1 for the Nether.
pub fn dimension(&self) -> i8 {
match self {
Self::Overworld(_) => 0,
Self::Nether(_) => -1,
}
}
/// The biomes of an area laid out x-major; the Nether is all `Hell`.
pub fn biomes(&self, x: i32, z: i32, width: usize, depth: usize) -> Vec<common::biome::Biome> {
match self {
Self::Overworld(generator) => generator.climate.sample(x, z, width, depth).biomes,
Self::Nether(_) => vec![common::biome::Biome::Hell; width * depth],
}
}
/// `provideChunk`: the blocks, and for the Nether the generator random afterwards.
pub fn generate(&self, pos: ChunkPos) -> (Box<[Block; VOLUME]>, Option<JavaRandom>) {
match self {
Self::Overworld(generator) => (generator.generate(pos), None),
Self::Nether(generator) => generator.generate(pos),
}
}
}
/// The random `provideChunk` builds the surface with.
pub(crate) fn chunk_random(pos: ChunkPos) -> JavaRandom {
JavaRandom::new(
i64::from(pos.x)
.wrapping_mul(341_873_128_712)
.wrapping_add(i64::from(pos.z).wrapping_mul(132_897_987_541)),
)
}
/// The limit noises and the selector blending them, which both dimensions shape terrain with.
pub(crate) struct Density {
lower_limit: PerlinOctaves,
upper_limit: PerlinOctaves,
selector: PerlinOctaves,
}
impl Density {
pub(crate) fn new(random: &mut JavaRandom) -> Self {
Self {
lower_limit: PerlinOctaves::new(random, 16),
upper_limit: PerlinOctaves::new(random, 16),
selector: PerlinOctaves::new(random, 8),
}
}
/// The density at the 5x17x5 samples of a chunk, minus `offsets` of each sample column.
pub(crate) fn field(
&self,
pos: ChunkPos,
vertical: f64,
selector_vertical: f64,
offsets: impl Fn(usize) -> [f64; SAMPLES_Y],
) -> Vec<f64> {
let origin = [f64::from(pos.x * 4), 0.0, f64::from(pos.z * 4)];
let sample = |noise: &PerlinOctaves, horizontal, vertical| {
let mut values = vec![0.0; SAMPLES_XZ * SAMPLES_Y * SAMPLES_XZ];
let size = [SAMPLES_XZ, SAMPLES_Y, SAMPLES_XZ];
noise.fill(
&mut values,
origin,
size,
[horizontal, vertical, horizontal],
);
values
};
let selector = sample(&self.selector, HORIZONTAL / 80.0, selector_vertical);
let lower = sample(&self.lower_limit, HORIZONTAL, vertical);
let upper = sample(&self.upper_limit, HORIZONTAL, vertical);
let mut field = Vec::with_capacity(lower.len());
for column in 0..SAMPLES_XZ * SAMPLES_XZ {
for (sy, offset) in offsets(column).into_iter().enumerate() {
let i = field.len();
let (low, high) = (lower[i] / 512.0, upper[i] / 512.0);
let blend = (selector[i] / 10.0 + 1.0) / 2.0;
let mut value = if blend < 0.0 {
low
} else if blend > 1.0 {
high
} else {
low + (high - low) * blend
};
value -= offset;
if sy > SAMPLES_Y - 4 {
let fade = f64::from((sy - (SAMPLES_Y - 4)) as f32 / 3.0);
value = value * (1.0 - fade) + -10.0 * fade;
}
field.push(value);
}
}
field
}
}
/// `generateTerrain`: trilinear interpolation of the density field; `block(x, y, z, density)`
/// picks each block.
pub(crate) fn interpolate(
density: &[f64],
blocks: &mut [Block; VOLUME],
block: impl Fn(usize, usize, usize, f64) -> Block,
) {
let sample = |x: usize, z: usize, y: usize| density[(x * SAMPLES_XZ + z) * SAMPLES_Y + y];
for cx in 0..4 {
for cz in 0..4 {
for cy in 0..SAMPLES_Y - 1 {
let mut d00 = sample(cx, cz, cy);
let mut d01 = sample(cx, cz + 1, cy);
let mut d10 = sample(cx + 1, cz, cy);
let mut d11 = sample(cx + 1, cz + 1, cy);
let step00 = (sample(cx, cz, cy + 1) - d00) * 0.125;
let step01 = (sample(cx, cz + 1, cy + 1) - d01) * 0.125;
let step10 = (sample(cx + 1, cz, cy + 1) - d10) * 0.125;
let step11 = (sample(cx + 1, cz + 1, cy + 1) - d11) * 0.125;
for sy in 0..8 {
let y = cy * 8 + sy;
let (mut d0, mut d1) = (d00, d01);
let step_x0 = (d10 - d00) * 0.25;
let step_x1 = (d11 - d01) * 0.25;
for sx in 0..4 {
let x = cx * 4 + sx;
let mut d = d0;
let step_z = (d1 - d0) * 0.25;
for sz in 0..4 {
let z = cz * 4 + sz;
blocks[chunk::index(x, y, z)] = block(x, y, z, d);
d += step_z;
}
d0 += step_x0;
d1 += step_x1;
}
d00 += step00;
d01 += step01;
d10 += step10;
d11 += step11;
}
}
}
}
}
/// The noises of `replaceBlocksForBiome` per column: sand (soul sand), gravel and soil depth.
pub(crate) fn surface_noise(
pair: &PerlinOctaves,
depth: &PerlinOctaves,
pos: ChunkPos,
) -> [[f64; 256]; 3] {
const SCALE: f64 = 1.0 / 32.0;
let (x0, z0) = (f64::from(pos.min_block_x()), f64::from(pos.min_block_z()));
let mut noise = [[0.0; 256]; 3];
let flat = [16, 16, 1];
pair.fill(&mut noise[0], [x0, z0, 0.0], flat, [SCALE, SCALE, 1.0]);
pair.fill(
&mut noise[1],
[x0, 109.0134, z0],
[16, 1, 16],
[SCALE, 1.0, SCALE],
);
depth.fill(&mut noise[2], [x0, z0, 0.0], flat, [SCALE * 2.0; 3]);
noise
}
/// Port of `ChunkProviderGenerate`'s terrain stage. Immutable, so one instance
/// can generate chunks on many threads at once.
pub struct OverworldGenerator {
seed: i64,
pub(crate) climate: Climate,
density: Density,
sand_gravel: PerlinOctaves,
soil_depth: PerlinOctaves,
scale: PerlinOctaves,
depth: PerlinOctaves,
/// `mobSpawnerNoise`, which population uses to vary tree density.
pub(crate) tree_noise: PerlinOctaves,
caves: Caves,
}
impl OverworldGenerator {
pub fn new(seed: i64) -> Self {
let mut random = JavaRandom::new(seed);
Self {
seed,
climate: Climate::new(seed),
density: Density::new(&mut random),
sand_gravel: PerlinOctaves::new(&mut random, 4),
soil_depth: PerlinOctaves::new(&mut random, 4),
scale: PerlinOctaves::new(&mut random, 10),
depth: PerlinOctaves::new(&mut random, 16),
tree_noise: PerlinOctaves::new(&mut random, 8),
caves: Caves {
seed,
nether: false,
},
}
}
/// Port of `provideChunk`.
pub fn generate(&self, pos: ChunkPos) -> Box<[Block; VOLUME]> {
let mut random = chunk_random(pos);
let climate = self
.climate
.sample(pos.min_block_x(), pos.min_block_z(), 16, 16);
let mut blocks = chunk::empty_blocks();
let density = self.density_field(pos, &climate);
interpolate(&density, &mut blocks, |x, y, z, d| {
if d > 0.0 {
Block::STONE
} else if y >= SEA_LEVEL {
Block::AIR
} else if climate.temperature[x * 16 + z] < 0.5 && y >= SEA_LEVEL - 1 {
Block::ICE
} else {
Block::WATER
}
});
self.replace_surface(pos, &climate, &mut random, &mut blocks);
self.caves.carve(pos, &mut blocks);
blocks
}
/// Port of `initializeNoiseField`.
fn density_field(&self, pos: ChunkPos, climate: &ClimateMap) -> Vec<f64> {
let (x, z) = (pos.x * 4, pos.z * 4);
let size = [SAMPLES_XZ, SAMPLES_XZ];
let mut scale = [0.0; SAMPLES_XZ * SAMPLES_XZ];
let mut depth = [0.0; SAMPLES_XZ * SAMPLES_XZ];
self.scale.fill_2d(&mut scale, x, z, size, [1.121, 1.121]);
self.depth.fill_2d(&mut depth, x, z, size, [200.0, 200.0]);
self.density
.field(pos, HORIZONTAL, HORIZONTAL / 160.0, |column| {
let stride = 16 / SAMPLES_XZ;
let column_x = column / SAMPLES_XZ * stride + stride / 2;
let column_z = column % SAMPLES_XZ * stride + stride / 2;
let temperature = climate.temperature[column_x * 16 + column_z];
let wetness = climate.rainfall[column_x * 16 + column_z] * temperature;
let dryness = (1.0 - wetness) * (1.0 - wetness);
let dryness = dryness * dryness;
let mut scale = ((scale[column] + 256.0) / 512.0 * (1.0 - dryness)).min(1.0);
let mut depth = depth[column] / 8000.0;
if depth < 0.0 {
depth = -depth * 0.3;
}
depth = depth * 3.0 - 2.0;
if depth < 0.0 {
depth = (depth / 2.0).max(-1.0) / 1.4 / 2.0;
scale = 0.0;
} else {
depth = depth.min(1.0) / 8.0;
}
let scale = scale.max(0.0) + 0.5;
let center = SAMPLES_Y as f64 / 2.0 + depth * SAMPLES_Y as f64 / 16.0 * 4.0;
std::array::from_fn(|sy| {
let offset = (sy as f64 - center) * 12.0 / scale;
if offset < 0.0 { offset * 4.0 } else { offset }
})
})
}
/// Port of `replaceBlocksForBiome`: topsoil, beaches, sandstone and bedrock.
fn replace_surface(
&self,
pos: ChunkPos,
climate: &ClimateMap,
random: &mut JavaRandom,
blocks: &mut [Block; VOLUME],
) {
let [sand, gravel, soil] = surface_noise(&self.sand_gravel, &self.soil_depth, pos);
for z in 0..16 {
for x in 0..16 {
let column = x * 16 + z;
let biome = climate.biomes[column];
let has_sand = sand[column] + random.next_double() * 0.2 > 0.0;
let has_gravel = gravel[column] + random.next_double() * 0.2 > 3.0;
let soil_depth = (soil[column] / 3.0 + 3.0 + random.next_double() * 0.25) as i32;
let mut remaining = -1;
let mut top = biome.top_block();
let mut filler = biome.filler_block();
for y in (0..HEIGHT).rev() {
let index = chunk::index(x, y, z);
if y as i32 <= random.next_int(5) {
blocks[index] = Block::BEDROCK;
continue;
}
let current = blocks[index];
if current.is_air() {
remaining = -1;
} else if current == Block::STONE {
if remaining == -1 {
if soil_depth <= 0 {
(top, filler) = (Block::AIR, Block::STONE);
} else if (SEA_LEVEL - 4..=SEA_LEVEL + 1).contains(&y) {
(top, filler) = (biome.top_block(), biome.filler_block());
if has_gravel {
(top, filler) = (Block::AIR, Block::GRAVEL);
}
if has_sand {
(top, filler) = (Block::SAND, Block::SAND);
}
}
if y < SEA_LEVEL && top.is_air() {
top = Block::WATER;
}
remaining = soil_depth;
blocks[index] = if y >= SEA_LEVEL - 1 { top } else { filler };
} else if remaining > 0 {
remaining -= 1;
blocks[index] = filler;
if remaining == 0 && filler == Block::SAND {
remaining = random.next_int(4);
filler = Block::SANDSTONE;
}
}
}
}
}
}
}
}