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 / common / src / physics.rs

//! Ports of `AxisAlignedBB`, the intercept helpers of `Vec3D` and `MovingObjectPosition`.

use glam::DVec3;

#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub struct Aabb {
    pub min: DVec3,
    pub max: DVec3,
}

impl Aabb {
    pub const fn new(min: DVec3, max: DVec3) -> Self {
        Self { min, max }
    }

    pub fn from_coords(x1: f64, y1: f64, z1: f64, x2: f64, y2: f64, z2: f64) -> Self {
        Self::new(DVec3::new(x1, y1, z1), DVec3::new(x2, y2, z2))
    }

    /// `addCoord`: grown in the direction of a movement.
    pub fn expand_towards(&self, d: DVec3) -> Self {
        let mut b = *self;
        for axis in 0..3 {
            if d[axis] < 0.0 {
                b.min[axis] += d[axis];
            }
            if d[axis] > 0.0 {
                b.max[axis] += d[axis];
            }
        }
        b
    }

    /// `expand`: grown by `d` on every side.
    pub fn inflate(&self, d: DVec3) -> Self {
        Self::new(self.min - d, self.max + d)
    }

    /// `contract`.
    pub fn deflate(&self, d: DVec3) -> Self {
        Self::new(self.min + d, self.max - d)
    }

    /// `getOffsetBoundingBox`.
    pub fn offset(&self, d: DVec3) -> Self {
        Self::new(self.min + d, self.max + d)
    }

    fn clip(&self, other: &Self, axis: usize, mut delta: f64) -> f64 {
        let (a, b) = ((axis + 1) % 3, (axis + 2) % 3);
        let overlaps = |i: usize| other.max[i] > self.min[i] && other.min[i] < self.max[i];
        if !overlaps(a) || !overlaps(b) {
            return delta;
        }
        if delta > 0.0 && other.max[axis] <= self.min[axis] {
            delta = delta.min(self.min[axis] - other.max[axis]);
        }
        if delta < 0.0 && other.min[axis] >= self.max[axis] {
            delta = delta.max(self.max[axis] - other.min[axis]);
        }
        delta
    }

    /// `calculateXOffset`: how far `other` may move along x before hitting this box.
    pub fn clip_x(&self, other: &Self, dx: f64) -> f64 {
        self.clip(other, 0, dx)
    }

    pub fn clip_y(&self, other: &Self, dy: f64) -> f64 {
        self.clip(other, 1, dy)
    }

    pub fn clip_z(&self, other: &Self, dz: f64) -> f64 {
        self.clip(other, 2, dz)
    }

    /// `intersectsWith`: open intervals on every axis.
    pub fn intersects(&self, other: &Self) -> bool {
        (0..3).all(|i| other.max[i] > self.min[i] && other.min[i] < self.max[i])
    }

    /// `isVecInside`.
    pub fn contains(&self, p: DVec3) -> bool {
        (0..3).all(|i| p[i] > self.min[i] && p[i] < self.max[i])
    }

    pub fn average_edge(&self) -> f64 {
        let d = self.max - self.min;
        (d.x + d.y + d.z) / 3.0
    }

    /// `calculateIntercept`: the nearest face hit by the segment and its side.
    pub fn intercept(&self, start: DVec3, end: DVec3) -> Option<(DVec3, u8)> {
        nearest_face(start, end, self.min, self.max)
    }
}

/// `Vec3D.getIntermediateWithXValue` and friends: the point of the segment where an axis has
/// a value, if within the segment.
pub fn intermediate(start: DVec3, end: DVec3, axis: usize, value: f64) -> Option<DVec3> {
    let d = end - start;
    if d[axis] * d[axis] < f64::from(1.0e-7_f32) {
        return None;
    }
    let t = (value - start[axis]) / d[axis];
    (0.0..=1.0).contains(&t).then(|| start + d * t)
}

/// The face intercepts of `collisionRayTrace`/`calculateIntercept` for a box, nearest first by
/// distance from `start`, with the side numbers 4, 5, 0, 1, 2, 3 for -x, +x, -y, +y, -z, +z.
pub fn nearest_face(start: DVec3, end: DVec3, min: DVec3, max: DVec3) -> Option<(DVec3, u8)> {
    let candidates = [
        (0, min.x, 4),
        (0, max.x, 5),
        (1, min.y, 0),
        (1, max.y, 1),
        (2, min.z, 2),
        (2, max.z, 3),
    ];
    let mut best: Option<(DVec3, u8)> = None;
    for (axis, value, side) in candidates {
        let Some(p) = intermediate(start, end, axis, value) else {
            continue;
        };
        let (a, b) = ((axis + 1) % 3, (axis + 2) % 3);
        let inside = |i: usize| p[i] >= min[i] && p[i] <= max[i];
        if !inside(a) || !inside(b) {
            continue;
        }
        if best.is_none_or(|(q, _)| start.distance_squared(p) < start.distance_squared(q)) {
            best = Some((p, side));
        }
    }
    best
}

/// `MovingObjectPosition` for a block.
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct BlockHit {
    pub x: i32,
    pub y: i32,
    pub z: i32,
    pub side: u8,
    pub point: DVec3,
}