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 / populate / trees.rs

//! Ports of `WorldGenTrees`, `WorldGenForest`, `WorldGenTaiga1` and `WorldGenTaiga2`.

use common::JavaRandom;
use common::biome::Biome;
use common::world::Block;

use super::big_tree;
use crate::level::Level;

#[derive(Clone, Copy, Debug)]
pub enum Kind {
    Oak,
    Birch,
    BigOak,
    /// `WorldGenBigTree` as saplings grow it, unscaled.
    SaplingBigOak,
    /// `WorldGenTaiga1`.
    Pine,
    /// `WorldGenTaiga2`.
    Spruce,
}

impl Kind {
    /// `BiomeGenBase.getRandomWorldGenForTrees`.
    pub fn pick(biome: Biome, rand: &mut JavaRandom) -> Self {
        match biome {
            Biome::Forest if rand.next_int(5) == 0 => Self::Birch,
            Biome::Forest | Biome::Rainforest if rand.next_int(3) == 0 => Self::BigOak,
            Biome::Forest | Biome::Rainforest => Self::Oak,
            Biome::Taiga if rand.next_int(3) == 0 => Self::Pine,
            Biome::Taiga => Self::Spruce,
            _ if rand.next_int(10) == 0 => Self::BigOak,
            _ => Self::Oak,
        }
    }

    pub fn generate(self, level: &mut Level, pos: [i32; 3], rand: &mut JavaRandom) -> bool {
        match self {
            Self::Oak => small(level, pos, rand, 4, None),
            Self::Birch => small(level, pos, rand, 5, Some(2)),
            Self::BigOak => big_tree::generate(level, pos, rand, 5),
            Self::SaplingBigOak => big_tree::generate(level, pos, rand, 4),
            Self::Pine => pine(level, pos, rand),
            Self::Spruce => spruce(level, pos, rand),
        }
    }
}

/// Whether the tree fits: in height, with each layer's square of `radius(y)` around the column
/// only air or leaves. If so, turns the soil below into dirt, or fails if it is not grass or dirt.
fn plant(level: &mut Level, [x, y, z]: [i32; 3], height: i32, radius: impl Fn(i32) -> i32) -> bool {
    if y < 1 || y + height + 1 > 128 {
        return false;
    }
    for cy in y..=y + 1 + height {
        if cy >= 128 {
            return false;
        }
        let radius = radius(cy);
        for cx in x - radius..=x + radius {
            for cz in z - radius..=z + radius {
                if !matches!(level.block(cx, cy, cz), Block::AIR | Block::LEAVES) {
                    return false;
                }
            }
        }
    }
    let soil = level.block(x, y - 1, z);
    if !matches!(soil, Block::GRASS | Block::DIRT) || y >= 128 - height - 1 {
        return false;
    }
    level.set_block(x, y - 1, z, Block::DIRT);
    true
}

fn trunk(level: &mut Level, [x, y, z]: [i32; 3], height: i32, metadata: Option<u8>) {
    for dy in 0..height {
        if matches!(level.block(x, y + dy, z), Block::AIR | Block::LEAVES) {
            level.change_block(x, y + dy, z, Block::LOG, metadata);
        }
    }
}

/// `WorldGenTrees` (oak) and `WorldGenForest` (birch).
fn small(
    level: &mut Level,
    [x, y, z]: [i32; 3],
    rand: &mut JavaRandom,
    base_height: i32,
    metadata: Option<u8>,
) -> bool {
    let height = rand.next_int(3) + base_height;
    let radius = |cy| match cy - y {
        dy if dy >= height - 1 => 2,
        0 => 0,
        _ => 1,
    };
    if !plant(level, [x, y, z], height, radius) {
        return false;
    }
    for cy in y - 3 + height..=y + height {
        let offset = cy - (y + height);
        let radius = 1 - offset / 2;
        for cx in x - radius..=x + radius {
            for cz in z - radius..=z + radius {
                let corner = (cx - x).abs() == radius && (cz - z).abs() == radius;
                if (!corner || (rand.next_int(2) != 0 && offset != 0))
                    && !level.block(cx, cy, cz).is_opaque_cube()
                {
                    level.change_block(cx, cy, cz, Block::LEAVES, metadata);
                }
            }
        }
    }
    trunk(level, [x, y, z], height, metadata);
    true
}

/// `WorldGenTaiga1`: a tall trunk with a single narrow cone at the top.
fn pine(level: &mut Level, [x, y, z]: [i32; 3], rand: &mut JavaRandom) -> bool {
    let height = rand.next_int(5) + 7;
    let bare = height - rand.next_int(2) - 3;
    let crown = height - bare;
    let max_radius = 1 + rand.next_int(crown + 1);
    let radius = |cy| if cy - y < bare { 0 } else { max_radius };
    if !plant(level, [x, y, z], height, radius) {
        return false;
    }
    let mut radius = 0;
    for cy in (y + bare..=y + height).rev() {
        leaf_square(level, [x, cy, z], radius);
        if radius >= 1 && cy == y + bare + 1 {
            radius -= 1;
        } else if radius < max_radius {
            radius += 1;
        }
    }
    trunk(level, [x, y, z], height - 1, Some(1));
    true
}

/// `WorldGenTaiga2`: stacked rings of alternating width.
fn spruce(level: &mut Level, [x, y, z]: [i32; 3], rand: &mut JavaRandom) -> bool {
    let height = rand.next_int(4) + 6;
    let bare = 1 + rand.next_int(2);
    let crown = height - bare;
    let max_radius = 2 + rand.next_int(2);
    let radius = |cy| if cy - y < bare { 0 } else { max_radius };
    if !plant(level, [x, y, z], height, radius) {
        return false;
    }
    let mut radius = rand.next_int(2);
    let mut limit = 1;
    let mut restart = 0;
    for i in 0..=crown {
        leaf_square(level, [x, y + height - i, z], radius);
        if radius >= limit {
            radius = restart;
            restart = 1;
            limit = (limit + 1).min(max_radius);
        } else {
            radius += 1;
        }
    }
    let cut = rand.next_int(3);
    trunk(level, [x, y, z], height - cut, Some(1));
    true
}

/// A square of spruce leaves without its corners, or a single block for radius 0.
fn leaf_square(level: &mut Level, [x, y, z]: [i32; 3], radius: i32) {
    for cx in x - radius..=x + radius {
        for cz in z - radius..=z + radius {
            let corner = (cx - x).abs() == radius && (cz - z).abs() == radius;
            if (!corner || radius <= 0) && !level.block(cx, y, cz).is_opaque_cube() {
                level.set_block_with_metadata(cx, y, cz, Block::LEAVES, 1);
            }
        }
    }
}