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

//! Port of `WorldGenBigTree`.

use common::JavaRandom;
use common::math::floor_f64;
use common::world::Block;

use crate::level::Level;

/// The two axes other than the major one, indexed by `major` and `major + 3`.
const OTHER_AXES: [usize; 6] = [2, 0, 0, 1, 2, 1];
const HEIGHT_LIMIT_LIMIT: i32 = 12;
const HEIGHT_ATTENUATION: f64 = 0.618;
const BRANCH_SLOPE: f64 = 0.381;
const SCALE_WIDTH: f64 = 1.0;
const LEAF_DENSITY: f64 = 1.0;
/// The original's truncated pi, which changes branch angles slightly.
#[allow(clippy::approx_constant)]
const PI: f64 = 3.14159;

struct BigTree<'a> {
    level: &'a mut Level,
    rand: JavaRandom,
    base: [i32; 3],
    height_limit: i32,
    height: i32,
    /// `leafDistanceLimit`: the height of a leaf cluster.
    leaf_distance: i32,
    /// Leaf cluster positions and the height their branch starts at.
    nodes: Vec<[i32; 4]>,
}

/// Population raises the leaf clusters from four to five layers with `setScale(1, 1, 1)`.
pub fn generate(
    level: &mut Level,
    [x, y, z]: [i32; 3],
    random: &mut JavaRandom,
    leaf_distance: i32,
) -> bool {
    let mut rand = JavaRandom::new(random.next_long());
    let height_limit = 5 + rand.next_int(HEIGHT_LIMIT_LIMIT);
    let mut tree = BigTree {
        level,
        rand,
        base: [x, y, z],
        height_limit,
        height: 0,
        leaf_distance,
        nodes: Vec::new(),
    };
    if !tree.valid_location() {
        return false;
    }
    tree.generate_leaf_nodes();
    for i in 0..tree.nodes.len() {
        let [x, y, z, _] = tree.nodes[i];
        for layer in 0..tree.leaf_distance {
            tree.leaf_layer(x, y + layer, z, tree.leaf_size(layer));
        }
    }
    tree.place_line([x, y, z], [x, y + tree.height, z]);
    // `generateLeafNodeBases`: branches from the trunk to the higher leaf clusters
    for i in 0..tree.nodes.len() {
        let [nx, ny, nz, branch_y] = tree.nodes[i];
        if f64::from(branch_y - y) >= f64::from(tree.height_limit) * 0.2 {
            tree.place_line([x, branch_y, z], [nx, ny, nz]);
        }
    }
    true
}

impl BigTree<'_> {
    fn block(&mut self, pos: [i32; 3]) -> Block {
        self.level.block(pos[0], pos[1], pos[2])
    }

    /// `generateLeafNodeList`.
    fn generate_leaf_nodes(&mut self) {
        self.height =
            ((f64::from(self.height_limit) * HEIGHT_ATTENUATION) as i32).min(self.height_limit - 1);
        let per_layer =
            ((1.382 + (LEAF_DENSITY * f64::from(self.height_limit) / 13.0).powi(2)) as i32).max(1);
        let mut y = self.base[1] + self.height_limit - self.leaf_distance;
        let trunk_top = self.base[1] + self.height;
        let mut layer = y - self.base[1];
        self.nodes.push([self.base[0], y, self.base[2], trunk_top]);
        y -= 1;
        while layer >= 0 {
            let size = self.layer_size(layer);
            if size >= 0.0 {
                for _ in 0..per_layer {
                    let distance =
                        SCALE_WIDTH * f64::from(size) * (f64::from(self.rand.next_float()) + 0.328);
                    let angle = f64::from(self.rand.next_float()) * 2.0 * PI;
                    let nx = floor_f64(distance * angle.sin() + f64::from(self.base[0]) + 0.5);
                    let nz = floor_f64(distance * angle.cos() + f64::from(self.base[2]) + 0.5);
                    let node = [nx, y, nz];
                    if self.check_line(node, [nx, y + self.leaf_distance, nz]) != -1 {
                        continue;
                    }
                    let dx = f64::from((self.base[0] - nx).abs());
                    let dz = f64::from((self.base[2] - nz).abs());
                    let rise = (dx.powi(2) + dz.powi(2)).sqrt() * BRANCH_SLOPE;
                    let branch_y = if f64::from(y) - rise > f64::from(trunk_top) {
                        trunk_top
                    } else {
                        (f64::from(y) - rise) as i32
                    };
                    if self.check_line([self.base[0], branch_y, self.base[2]], node) == -1 {
                        self.nodes.push([nx, y, nz, branch_y]);
                    }
                }
            }
            y -= 1;
            layer -= 1;
        }
    }

    /// `layerSize`: the crown radius at a height above the base, negative below the crown.
    fn layer_size(&self, height: i32) -> f32 {
        if f64::from(height) < f64::from(self.height_limit as f32) * 0.3 {
            return -1.618;
        }
        let half = self.height_limit as f32 / 2.0;
        let offset = self.height_limit as f32 / 2.0 - height as f32;
        let radius = if offset == 0.0 {
            half
        } else if offset.abs() >= half {
            0.0
        } else {
            (f64::from(half.abs()).powi(2) - f64::from(offset.abs()).powi(2)).sqrt() as f32
        };
        radius * 0.5
    }

    /// `leafSize`.
    fn leaf_size(&self, layer: i32) -> f32 {
        if !(0..self.leaf_distance).contains(&layer) {
            -1.0
        } else if layer == 0 || layer == self.leaf_distance - 1 {
            2.0
        } else {
            3.0
        }
    }

    /// `genTreeLayer` with the y axis as the major axis: a disc of leaves.
    fn leaf_layer(&mut self, x: i32, y: i32, z: i32, radius: f32) {
        let extent = (f64::from(radius) + 0.618) as i32;
        for dx in -extent..=extent {
            for dz in -extent..=extent {
                let distance = ((f64::from(dx.abs()) + 0.5).powi(2)
                    + (f64::from(dz.abs()) + 0.5).powi(2))
                .sqrt();
                if distance > f64::from(radius) {
                    continue;
                }
                let pos = [x + dx, y, z + dz];
                if matches!(self.block(pos), Block::AIR | Block::LEAVES) {
                    self.level.set_block(pos[0], pos[1], pos[2], Block::LEAVES);
                }
            }
        }
    }

    /// `placeBlockLine` with logs.
    fn place_line(&mut self, from: [i32; 3], to: [i32; 3]) {
        for (_, [x, y, z]) in line(from, to, 0.5) {
            self.level.set_block(x, y, z, Block::LOG);
        }
    }

    /// `checkBlockLine`: -1 if the line is clear of everything but air and leaves,
    /// otherwise the distance to the first obstruction.
    fn check_line(&mut self, from: [i32; 3], to: [i32; 3]) -> i32 {
        line(from, to, 0.0)
            .find(|&(_, pos)| !matches!(self.block(pos), Block::AIR | Block::LEAVES))
            .map_or(-1, |(i, _)| i.abs())
    }

    /// `validTreeLocation`: needs grass or dirt below and at least six free blocks above.
    fn valid_location(&mut self) -> bool {
        let [x, y, z] = self.base;
        if !matches!(self.block([x, y - 1, z]), Block::GRASS | Block::DIRT) {
            return false;
        }
        match self.check_line(self.base, [x, y + self.height_limit - 1, z]) {
            -1 => true,
            free if free < 6 => false,
            free => {
                self.height_limit = free;
                true
            }
        }
    }
}

/// The blocks of a line, stepping along its dominant axis, with the signed step count.
fn line(from: [i32; 3], to: [i32; 3], bias: f64) -> impl Iterator<Item = (i32, [i32; 3])> {
    let delta = [0, 1, 2].map(|axis| to[axis] - from[axis]);
    let mut major = 0;
    for axis in 0..3 {
        if delta[axis].abs() > delta[major].abs() {
            major = axis;
        }
    }
    let (a, b) = (OTHER_AXES[major], OTHER_AXES[major + 3]);
    let step = delta[major].signum();
    let ratio_a = f64::from(delta[a]) / f64::from(delta[major]);
    let ratio_b = f64::from(delta[b]) / f64::from(delta[major]);
    let steps = if step == 0 { 0 } else { delta[major].abs() + 1 };
    (0..steps).map(move |n| {
        let i = n * step;
        let mut pos = [0; 3];
        pos[major] = floor_f64(f64::from(from[major] + i) + bias);
        pos[a] = floor_f64(f64::from(from[a]) + f64::from(i) * ratio_a + bias);
        pos[b] = floor_f64(f64::from(from[b]) + f64::from(i) * ratio_b + bias);
        (i, pos)
    })
}