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 / 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()
}