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 caves.rs
use std::f32::consts::PI;
use common::world::chunk::VOLUME;
use common::world::{Block, ChunkPos};
use common::{JavaRandom, math};
const RANGE: i32 = 8;
/// Port of `MapGenCaves` and `MapGenCavesHell` (with `MapGenBase.generate`).
pub struct Caves {
pub seed: i64,
pub nether: bool,
}
/// One tunnel of the target chunk being carved.
struct Carver<'a> {
nether: bool,
chunk: ChunkPos,
blocks: &'a mut [Block; VOLUME],
}
impl Caves {
pub fn carve(&self, chunk: ChunkPos, blocks: &mut [Block; VOLUME]) {
let mut carver = Carver {
nether: self.nether,
chunk,
blocks,
};
let mut random = JavaRandom::new(self.seed);
let x_salt = random.next_long() / 2 * 2 + 1;
let z_salt = random.next_long() / 2 * 2 + 1;
for source_x in chunk.x - RANGE..=chunk.x + RANGE {
for source_z in chunk.z - RANGE..=chunk.z + RANGE {
let seed = i64::from(source_x)
.wrapping_mul(x_salt)
.wrapping_add(i64::from(source_z).wrapping_mul(z_salt))
^ self.seed;
random.set_seed(seed);
carver.carve_from(&mut random, ChunkPos::new(source_x, source_z));
}
}
}
}
impl Carver<'_> {
/// Port of `recursiveGenerate`.
fn carve_from(&mut self, random: &mut JavaRandom, source: ChunkPos) {
let (spread, rarity) = if self.nether { (10, 5) } else { (40, 15) };
let bound = random.next_int(spread) + 1;
let bound = random.next_int(bound) + 1;
let mut count = random.next_int(bound);
if random.next_int(rarity) != 0 {
count = 0;
}
for _ in 0..count {
let x = f64::from(source.min_block_x() + random.next_int(16));
let y = if self.nether {
f64::from(random.next_int(128))
} else {
let bound = random.next_int(120) + 8;
f64::from(random.next_int(bound))
};
let z = f64::from(source.min_block_z() + random.next_int(16));
let mut tunnels = 1;
if random.next_int(4) == 0 {
let width = 1.0 + random.next_float() * 6.0;
self.carve_tunnel(random, [x, y, z], width, [0.0, 0.0], [-1, -1], 0.5);
tunnels += random.next_int(4);
}
for _ in 0..tunnels {
let yaw = random.next_float() * PI * 2.0;
let pitch = (random.next_float() - 0.5) * 2.0 / 8.0;
let width = random.next_float() * 2.0;
let width = width + random.next_float();
let (scale, vertical_scale) = if self.nether { (2.0, 0.5) } else { (1.0, 1.0) };
let angles = [yaw, pitch];
self.carve_tunnel(
random,
[x, y, z],
width * scale,
angles,
[0, 0],
vertical_scale,
);
}
}
}
/// Port of `MapGenCaves.releaseEntitySkin` (misnamed by MCP; it carves one tunnel). A
/// `step` of -1 makes a round room.
fn carve_tunnel(
&mut self,
parent: &mut JavaRandom,
[mut x, mut y, mut z]: [f64; 3],
width: f32,
[mut yaw, mut pitch]: [f32; 2],
[mut step, mut length]: [i32; 2],
vertical_scale: f64,
) {
let center_x = f64::from(self.chunk.min_block_x() + 8);
let center_z = f64::from(self.chunk.min_block_z() + 8);
let mut yaw_change = 0.0_f32;
let mut pitch_change = 0.0_f32;
let mut random = JavaRandom::new(parent.next_long());
if length <= 0 {
let max = RANGE * 16 - 16;
length = max - random.next_int(max / 4);
}
let is_room = step == -1;
if is_room {
step = length / 2;
}
let branch_step = random.next_int(length / 2) + length / 4;
let steep = random.next_int(6) == 0;
while step < length {
let radius = 1.5 + f64::from(math::sin(step as f32 * PI / length as f32) * width * 1.0);
let half_height = radius * vertical_scale;
let pitch_cos = math::cos(pitch);
let pitch_sin = math::sin(pitch);
x += f64::from(math::cos(yaw) * pitch_cos);
y += f64::from(pitch_sin);
z += f64::from(math::sin(yaw) * pitch_cos);
pitch *= if steep { 0.92 } else { 0.7 };
pitch += pitch_change * 0.1;
yaw += yaw_change * 0.1;
pitch_change *= 0.9;
yaw_change *= 0.75;
pitch_change += jitter(&mut random) * 2.0;
yaw_change += jitter(&mut random) * 4.0;
if !is_room && step == branch_step && width > 1.0 {
for side in [-1.0, 1.0] {
let width = random.next_float() * 0.5 + 0.5;
let angles = [yaw + side * PI * 0.5, pitch / 3.0];
self.carve_tunnel(parent, [x, y, z], width, angles, [step, length], 1.0);
}
return;
}
if is_room || random.next_int(4) != 0 {
let dx = x - center_x;
let dz = z - center_z;
let remaining = f64::from(length - step);
let reach = f64::from(width + 2.0 + 16.0);
if dx * dx + dz * dz - remaining * remaining > reach * reach {
return;
}
if x >= center_x - 16.0 - radius * 2.0
&& z >= center_z - 16.0 - radius * 2.0
&& x <= center_x + 16.0 + radius * 2.0
&& z <= center_z + 16.0 + radius * 2.0
{
let carved = self.carve_ellipsoid([x, y, z], radius, half_height);
if carved && is_room {
break;
}
}
}
step += 1;
}
}
/// Clears one ellipsoid of the tunnel. Returns `false` if water (lava in the Nether) was
/// found nearby and nothing was carved.
fn carve_ellipsoid(&mut self, [x, y, z]: [f64; 3], radius: f64, half_height: f64) -> bool {
let (base_x, base_z) = (self.chunk.min_block_x(), self.chunk.min_block_z());
let blocks = &mut *self.blocks;
let liquids = if self.nether {
[Block::FLOWING_LAVA, Block::LAVA]
} else {
[Block::FLOWING_WATER, Block::WATER]
};
let min_x = (math::floor_f64(x - radius) - base_x - 1).max(0);
let max_x = (math::floor_f64(x + radius) - base_x + 1).min(16);
let min_y = (math::floor_f64(y - half_height) - 1).max(1);
let max_y = (math::floor_f64(y + half_height) + 1).min(120);
let min_z = (math::floor_f64(z - radius) - base_z - 1).max(0);
let max_z = (math::floor_f64(z + radius) - base_z + 1).min(16);
let column = |bx: i32, bz: i32| (bx * 16 + bz) * 128;
for bx in min_x..max_x {
for bz in min_z..max_z {
let mut by = max_y + 1;
while by >= min_y - 1 {
if (0..128).contains(&by) {
if liquids.contains(&blocks[(column(bx, bz) + by) as usize]) {
return false;
}
let on_shell = by == min_y - 1
|| bx == min_x
|| bx == max_x - 1
|| bz == min_z
|| bz == max_z - 1;
if !on_shell {
by = min_y;
}
}
by -= 1;
}
}
}
for bx in min_x..max_x {
let nx = (f64::from(bx + base_x) + 0.5 - x) / radius;
for bz in min_z..max_z {
let nz = (f64::from(bz + base_z) + 0.5 - z) / radius;
if nx * nx + nz * nz >= 1.0 {
continue;
}
// The original reads one block above the y it tests; kept for parity.
let mut index = (column(bx, bz) + max_y) as usize;
let mut exposed_grass = false;
for by in (min_y..max_y).rev() {
let ny = (f64::from(by) + 0.5 - y) / half_height;
if ny > -0.7 && nx * nx + ny * ny + nz * nz < 1.0 {
let block = blocks[index];
if block == Block::GRASS {
exposed_grass = true;
}
if self.nether {
if matches!(block, Block::NETHERRACK | Block::DIRT | Block::GRASS) {
blocks[index] = Block::AIR;
}
} else if matches!(block, Block::STONE | Block::DIRT | Block::GRASS) {
if by < 10 {
blocks[index] = Block::FLOWING_LAVA;
} else {
blocks[index] = Block::AIR;
if exposed_grass && blocks[index - 1] == Block::DIRT {
blocks[index - 1] = Block::GRASS;
}
}
}
}
index -= 1;
}
}
}
true
}
}
fn jitter(random: &mut JavaRandom) -> f32 {
let a = random.next_float();
let b = random.next_float();
(a - b) * random.next_float()
}