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 / entity / path.rs

use std::cmp::Reverse;
use std::collections::{BinaryHeap, VecDeque};

use common::entity::Entity;
use common::math::{floor_f32, floor_f64};
use common::world::shape::BlockView;
use common::world::{Block, ChunkPos, Material};
use glam::DVec3;
use rustc_hash::FxHashMap;

use crate::level::Level;

type Position = [i32; 3];

#[derive(Clone, Copy)]
struct Node {
    cost: f32,
    previous: Option<Position>,
    closed: bool,
}

fn distance(a: Position, b: Position) -> f32 {
    let d = std::array::from_fn::<_, 3, _>(|i| (a[i] - b[i]) as f32);
    (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt()
}

fn clearance(level: &Level, [x, y, z]: Position, size: Position) -> i8 {
    if y < 0 {
        return 0;
    }
    // `ChunkCache` reads air above the world
    for x in x..x + size[0] {
        for y in y..(y + size[1]).min(128) {
            for z in z..z + size[2] {
                if !level.is_loaded(ChunkPos::containing(x, z)) {
                    return 0;
                }
                let block = BlockView::block(level, x, y, z);
                if matches!(block, Block::WOODEN_DOOR | Block::IRON_DOOR) {
                    if BlockView::metadata(level, x, y, z) & 4 == 0 {
                        return 0;
                    }
                } else if block.material().is_solid() {
                    return 0;
                } else if block.material() == Material::Water {
                    return -1;
                } else if block.material() == Material::Lava {
                    return -2;
                }
            }
        }
    }
    1
}

fn safe(level: &Level, [x, mut y, z]: Position, size: Position, step: i32) -> Option<Position> {
    if clearance(level, [x, y, z], size) != 1 {
        if step == 0 || clearance(level, [x, y + step, z], size) != 1 {
            return None;
        }
        y += step;
    }
    let mut fallen = 0;
    while y > 0 {
        match clearance(level, [x, y - 1, z], size) {
            -2 => return None,
            1 => {
                fallen += 1;
                if fallen >= 4 {
                    return None;
                }
                y -= 1;
            }
            _ => break,
        }
    }
    Some([x, y, z])
}

pub fn find(level: &Level, entity: &Entity, destination: DVec3, range: f32) -> VecDeque<DVec3> {
    let start = entity.bb.min.to_array().map(floor_f64);
    let half = f64::from(entity.width / 2.0);
    let goal = [
        floor_f64(destination.x - half),
        floor_f64(destination.y),
        floor_f64(destination.z - half),
    ];
    let width = floor_f32(entity.width + 1.0);
    let size = [width, floor_f32(entity.height + 1.0), width];
    // Scores are never negative, so their bits order like them; ties go to the earliest node
    let mut open = BinaryHeap::from([Reverse((distance(start, goal).to_bits(), 0, start))]);
    let mut nodes = FxHashMap::default();
    nodes.insert(
        start,
        Node {
            cost: 0.0,
            previous: None,
            closed: false,
        },
    );
    let mut best = start;
    let mut order = 0;
    while let Some(Reverse((_, _, position))) = open.pop() {
        let Some(node) = nodes.get_mut(&position) else {
            continue;
        };
        if node.closed {
            continue;
        }
        node.closed = true;
        let cost = node.cost;
        if distance(position, goal) < distance(best, goal) {
            best = position;
        }
        if position == goal {
            best = goal;
            break;
        }
        if nodes.len() >= 4096 {
            break;
        }
        let [x, y, z] = position;
        let step = i32::from(clearance(level, [x, y + 1, z], size) == 1);
        for candidate in [[x, y, z + 1], [x - 1, y, z], [x + 1, y, z], [x, y, z - 1]] {
            let Some(next) = safe(level, candidate, size, step) else {
                continue;
            };
            let remaining = distance(next, goal);
            if remaining >= range {
                continue;
            }
            let cost = cost + distance(position, next);
            if nodes
                .get(&next)
                .is_some_and(|node| node.closed || node.cost <= cost)
            {
                continue;
            }
            nodes.insert(
                next,
                Node {
                    cost,
                    previous: Some(position),
                    closed: false,
                },
            );
            order += 1;
            open.push(Reverse(((cost + remaining).to_bits(), order, next)));
        }
    }
    if best == start {
        return VecDeque::new();
    }
    let mut path: Vec<_> = std::iter::successors(Some(best), |p| nodes[p].previous).collect();
    path.reverse();
    path.into_iter()
        .map(|[x, y, z]| {
            let half = f64::from(width) * 0.5;
            DVec3::new(f64::from(x) + half, f64::from(y), f64::from(z) + half)
        })
        .collect()
}