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 / client / src / render / mesher / special.rs

use common::physics::Aabb;
use common::world::{Block, Shape, shape};
use glam::{DVec3, Vec3};

use super::block::{self, Bounds};
use super::{Builder, Cache, Light, MeshInput, Shade, WHITE, tile};

pub fn render(
    input: &MeshInput,
    cache: &Cache,
    builder: &mut Builder,
    block: Block,
    position: [i32; 3],
) {
    let [x, y, z] = position;
    let light = cache.light(x, y, z);
    let mut cuboid =
        |bounds| block::standard_bounds(input, cache, builder, block, position, bounds);
    match block.properties().map(|p| p.shape) {
        Some(Shape::Fire) => fire(
            cache,
            builder,
            position,
            block.properties().unwrap().texture,
        ),
        Some(Shape::Stairs) => {
            let mut boxes = Vec::with_capacity(2);
            let origin = DVec3::new(f64::from(x), f64::from(y), f64::from(z));
            let region = Aabb::new(origin, origin + DVec3::ONE);
            shape::collision_boxes(cache, block, x, y, z, &region, &mut boxes);
            for bounds in boxes {
                cuboid(Bounds::of(&bounds.offset(-origin)));
            }
        }
        Some(Shape::Door) => cuboid(Bounds::of(&shape::block_bounds(cache, block, x, y, z))),
        Some(Shape::Fence) => {
            cuboid(Bounds::new([0.375, 0.0, 0.375], [0.625, 1.0, 0.625]));
            let west = cache.block(x - 1, y, z) == block;
            let east = cache.block(x + 1, y, z) == block;
            let north = cache.block(x, y, z - 1) == block;
            let south = cache.block(x, y, z + 1) == block;
            let default_x = !west && !east && !north && !south;
            for bottom in [0.375, 0.75] {
                if west || east || default_x {
                    cuboid(Bounds::new(
                        [if west { 0.0 } else { 0.4375 }, bottom, 0.4375],
                        [if east { 1.0 } else { 0.5625 }, bottom + 0.1875, 0.5625],
                    ));
                }
                if north || south {
                    cuboid(Bounds::new(
                        [0.4375, bottom, if north { 0.0 } else { 0.4375 }],
                        [0.5625, bottom + 0.1875, if south { 1.0 } else { 0.5625 }],
                    ));
                }
            }
        }
        // `RenderBlocks.renderBlockLadder`
        Some(Shape::Ladder) => {
            let [u0, u1, v0, v1] = tile(block::texture(cache, block, position, 0, true));
            let corners = match cache.metadata(x, y, z) {
                5 => [
                    ([0.05, 1.0, 1.0], (u0, v0)),
                    ([0.05, 0.0, 1.0], (u0, v1)),
                    ([0.05, 0.0, 0.0], (u1, v1)),
                    ([0.05, 1.0, 0.0], (u1, v0)),
                ],
                4 => [
                    ([0.95, 0.0, 1.0], (u1, v1)),
                    ([0.95, 1.0, 1.0], (u1, v0)),
                    ([0.95, 1.0, 0.0], (u0, v0)),
                    ([0.95, 0.0, 0.0], (u0, v1)),
                ],
                3 => [
                    ([1.0, 0.0, 0.05], (u1, v1)),
                    ([1.0, 1.0, 0.05], (u1, v0)),
                    ([0.0, 1.0, 0.05], (u0, v0)),
                    ([0.0, 0.0, 0.05], (u0, v1)),
                ],
                2 => [
                    ([1.0, 1.0, 0.95], (u0, v0)),
                    ([1.0, 0.0, 0.95], (u0, v1)),
                    ([0.0, 0.0, 0.95], (u1, v1)),
                    ([0.0, 1.0, 0.95], (u1, v0)),
                ],
                _ => return,
            };
            let quad = corners.map(|(p, uv)| (Vec3::from_array(p), uv));
            builder.quad(quad, light, Shade::Top, WHITE);
        }
        Some(Shape::Crops) => {
            let texture = block::texture(cache, block, position, 0, true);
            let y = -0.0625;
            for offset in [0.25, 0.75] {
                plane(builder, [offset, y, 0.0], [offset, y, 1.0], texture, light);
                plane(builder, [0.0, y, offset], [1.0, y, offset], texture, light);
            }
        }
        _ => {}
    }
}

/// `RenderBlocks.renderBlockFire`: flames clinging to burnable walls and ceilings, or rising
/// from the floor.
fn fire(cache: &Cache, builder: &mut Builder, [x, y, z]: [i32; 3], texture: u8) {
    let light = Light { sky: 15, block: 15 };
    let burns = |dx: i32, dy: i32, dz: i32| cache.block(x + dx, y + dy, z + dz).fire_rates().0 > 0;
    let [u0, u1, v0, v1] = tile(texture);
    let [w0, w1] = [v0, v1].map(|v| v + 16.0 / 256.0);
    // A quad from (x, z) corners, the outer two at height `y0` and the inner two at `y1`
    let mut quad = |xz: [(f32, f32); 4], (y0, y1), (ua, ub), (va, vb)| {
        let corners = [(y0, ua, va), (y1, ua, vb), (y1, ub, vb), (y0, ub, va)];
        let quad = std::array::from_fn(|i| {
            let ((x, z), (y, u, v)) = (xz[i], corners[i]);
            (Vec3::new(x, y, z), (u, v))
        });
        builder.quad(quad, light, Shade::Top, WHITE);
    };
    let below = cache.block(x, y - 1, z);
    if !below.is_normal_cube() && !burns(0, -1, 0) {
        let (h, low, side) = (1.4 + 1.0 / 16.0, 1.0 / 16.0, 0.2);
        let (mut a0, mut a1, mut b0, mut b1) = (u0, u1, v0, v1);
        if (x + y + z) & 1 == 1 {
            (b0, b1) = (w0, w1);
        }
        if (x / 2 + y / 2 + z / 2) & 1 == 1 {
            (a0, a1) = (a1, a0);
        }
        let walls = [
            [(side, 1.0), (0.0, 1.0), (0.0, 0.0), (side, 0.0)],
            [(1.0 - side, 0.0), (1.0, 0.0), (1.0, 1.0), (1.0 - side, 1.0)],
            [(0.0, side), (0.0, 0.0), (1.0, 0.0), (1.0, side)],
            [(1.0, 1.0 - side), (1.0, 1.0), (0.0, 1.0), (0.0, 1.0 - side)],
        ];
        let sides = [(-1, 0), (1, 0), (0, -1), (0, 1)];
        for (i, ((dx, dz), [p, q, r, s])) in sides.into_iter().zip(walls).enumerate() {
            if burns(dx, 0, dz) {
                let us = if i % 2 == 0 { (a1, a0) } else { (a0, a1) };
                quad([p, q, r, s], (h, low), us, (b0, b1));
                quad([s, r, q, p], (h, low), (us.1, us.0), (b0, b1));
            }
        }
        if burns(0, 1, 0) {
            // Two slopes, turned by the block's parity
            let (top, tip) = (1.0, 0.8);
            let square = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)];
            let turn = if (x + y + 1 + z) & 1 == 0 { 0 } else { 3 };
            for (turn, vs) in [(turn, (v0, v1)), (turn + 2, (w0, w1))] {
                let xz = std::array::from_fn(|i| square[(i + turn) % 4]);
                quad(xz, (tip, top), (u1, u0), vs);
            }
        }
        return;
    }
    let h = 1.4;
    let planes = [
        [(0.2, 1.0), (0.7, 1.0), (0.7, 0.0), (0.2, 0.0)],
        [(0.8, 0.0), (0.3, 0.0), (0.3, 1.0), (0.8, 1.0)],
        [(1.0, 0.8), (1.0, 0.3), (0.0, 0.3), (0.0, 0.8)],
        [(0.0, 0.2), (0.0, 0.7), (1.0, 0.7), (1.0, 0.2)],
        [(0.1, 0.0), (0.0, 0.0), (0.0, 1.0), (0.1, 1.0)],
        [(0.9, 1.0), (1.0, 1.0), (1.0, 0.0), (0.9, 0.0)],
        [(0.0, 0.9), (0.0, 1.0), (1.0, 1.0), (1.0, 0.9)],
        [(1.0, 0.1), (1.0, 0.0), (0.0, 0.0), (0.0, 0.1)],
    ];
    for (i, xz) in planes.into_iter().enumerate() {
        let us = if i < 4 { (u1, u0) } else { (u0, u1) };
        let vs = if (2..6).contains(&i) {
            (w0, w1)
        } else {
            (v0, v1)
        };
        quad(xz, (h, 0.0), us, vs);
    }
}

/// A double-sided plane from `a` to `b`, a block high.
fn plane(builder: &mut Builder, a: [f32; 3], b: [f32; 3], texture: u8, light: Light) {
    let (a, b) = (Vec3::from_array(a), Vec3::from_array(b));
    let (u, v) = (
        f32::from(texture & 15) / 16.0,
        f32::from(texture >> 4) / 16.0,
    );
    let edge = 15.99 / 256.0;
    let [p, q, r, s] = [
        (a + Vec3::Y, (u, v)),
        (a, (u, v + edge)),
        (b, (u + edge, v + edge)),
        (b + Vec3::Y, (u + edge, v)),
    ];
    builder.quad([p, q, r, s], light, Shade::Top, WHITE);
    builder.quad([s, r, q, p], light, Shade::Top, WHITE);
}

pub fn torch(cache: &Cache, builder: &mut Builder, block: Block, [x, y, z]: [i32; 3]) {
    let (offset, tilt) = match cache.metadata(x, y, z) {
        1 => (Vec3::new(-0.1, 0.2, 0.0), Vec3::new(-0.4, 0.0, 0.0)),
        2 => (Vec3::new(0.1, 0.2, 0.0), Vec3::new(0.4, 0.0, 0.0)),
        3 => (Vec3::new(0.0, 0.2, -0.1), Vec3::new(0.0, 0.0, -0.4)),
        4 => (Vec3::new(0.0, 0.2, 0.1), Vec3::new(0.0, 0.0, 0.4)),
        _ => (Vec3::ZERO, Vec3::ZERO),
    };
    let light = if block.light_emission() > 0 {
        Light { sky: 15, block: 15 }
    } else {
        cache.light(x, y, z)
    };
    let texture = block.properties().unwrap().texture;
    torch_at(builder, offset, tilt, texture, light, Shade::Top);
}

/// `renderTorchAtAngle`: a torch standing at `offset`, its foot pushed out by `tilt`.
pub fn torch_at(
    builder: &mut Builder,
    offset: Vec3,
    tilt: Vec3,
    texture: u8,
    light: Light,
    shade: Shade,
) {
    let (u, v) = (
        f32::from(texture & 15) / 16.0,
        f32::from(texture >> 4) / 16.0,
    );
    let center = offset + Vec3::new(0.5, 0.625, 0.5) + tilt * 0.375;
    let cap = [
        (-1.0, -1.0, 7.0, 6.0),
        (-1.0, 1.0, 7.0, 8.0),
        (1.0, 1.0, 9.0, 8.0),
        (1.0, -1.0, 9.0, 6.0),
    ]
    .map(|(dx, dz, du, dv)| {
        let corner = center + Vec3::new(dx * 0.0625, 0.0, dz * 0.0625);
        (corner, (u + du / 256.0, v + dv / 256.0))
    });
    builder.quad(cap, light, shade, WHITE);
    for (a, b) in [
        (Vec3::new(0.4375, 0.0, 0.0), Vec3::new(0.4375, 0.0, 1.0)),
        (Vec3::new(0.5625, 0.0, 1.0), Vec3::new(0.5625, 0.0, 0.0)),
        (Vec3::new(0.0, 0.0, 0.5625), Vec3::new(1.0, 0.0, 0.5625)),
        (Vec3::new(1.0, 0.0, 0.4375), Vec3::new(0.0, 0.0, 0.4375)),
    ] {
        let edge = 15.99 / 256.0;
        builder.quad(
            [
                (a + offset + Vec3::Y, (u, v)),
                (a + offset + tilt, (u, v + edge)),
                (b + offset + tilt, (u + edge, v + edge)),
                (b + offset + Vec3::Y, (u + edge, v)),
            ],
            light,
            shade,
            WHITE,
        );
    }
}