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