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

//! Beds, levers, repeaters, redstone wire, rails and pistons: the special `RenderBlocks`
//! shapes of mechanisms.

use common::math;
use common::world::shape::SIDE_OFFSETS;
use common::world::{Block, bed, redstone, texture};
use glam::{DVec3, Vec3};

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

/// The light at a block with its own emission as the minimum (`Block.getBlockBrightness`).
fn brightness(cache: &Cache, block: Block, [x, y, z]: [i32; 3]) -> Light {
    cache.light(x, y, z).with_emission(block.light_emission())
}

// ---- Beds -------------------------------------------------------

/// `ModelBed.headInvisibleFace`: the side where the other half joins.
const HEAD_INVISIBLE_FACES: [usize; 4] = [3, 4, 2, 5];

/// `renderBlockBed`: flat-lit faces, the pillow top turned by the bed's direction.
pub fn bed(input: &MeshInput, cache: &Cache, builder: &mut Builder, position: [i32; 3]) {
    let [x, y, z] = position;
    let block = Block::BED;
    let metadata = cache.metadata(x, y, z);
    let direction = bed::direction(metadata);
    let bounds = Bounds::with_height(9.0 / 16.0);
    let own = brightness(cache, block, position);
    let face = |side| texture::face(block, metadata, side).0;
    let full = |texture: u8| {
        let (tx, ty) = (f64::from(texture & 15) * 16.0, f64::from(texture & 0xF0));
        let range = |t: f64| (f64::from(t as f32 / 256.0), (t + 16.0 - 0.01) / 256.0);
        let ((u0, u1), (v0, v1)) = (range(tx), range(ty));
        (u0, u1, v0, v1)
    };
    let uv = |u: f64, v: f64| (u as f32, v as f32);

    let (u0, u1, v0, v1) = full(face(0));
    let floor = 3.0 / 16.0;
    builder.quad(
        [
            (Vec3::new(0.0, floor, 1.0), uv(u0, v1)),
            (Vec3::new(0.0, floor, 0.0), uv(u0, v0)),
            (Vec3::new(1.0, floor, 0.0), uv(u1, v0)),
            (Vec3::new(1.0, floor, 1.0), uv(u1, v1)),
        ],
        own,
        Shade::Bottom,
        WHITE,
    );

    let (u0, u1, v0, v1) = full(face(1));
    // Corners (1, 1), (1, 0), (0, 0) and (0, 1) of the top, as (x, z)
    let uvs = match direction {
        0 => [(u1, v1), (u0, v1), (u0, v0), (u1, v0)],
        2 => [(u0, v0), (u1, v0), (u1, v1), (u0, v1)],
        3 => [(u1, v0), (u1, v1), (u0, v1), (u0, v0)],
        _ => [(u0, v1), (u0, v0), (u1, v0), (u1, v1)],
    };
    let quad = [0b111, 0b110, 0b010, 0b011].map(|corner| bounds.corner(corner));
    let quad = std::array::from_fn(|i| (quad[i], uv(uvs[i].0, uvs[i].1)));
    let above = brightness(cache, block, [x, y + 1, z]);
    builder.quad(quad, above, Shade::Top, WHITE);

    let hidden = if bed::is_head(metadata) {
        HEAD_INVISIBLE_FACES[bed::FOOT_INVISIBLE_FACES[direction] as usize]
    } else {
        HEAD_INVISIBLE_FACES[direction]
    };
    let flipped = match direction {
        0 => 5,
        1 => 3,
        3 => 2,
        _ => 4,
    };
    for (side, [dx, dy, dz]) in SIDE_OFFSETS.into_iter().enumerate().skip(2) {
        if side == hidden
            || !block::side_visible(cache, block, position, side, &bounds, input.fancy)
        {
            continue;
        }
        let orientation = Orientation {
            rotate: [0; 6],
            flip: side == flipped,
        };
        let quad = block::oriented_face_quad(side, &bounds, face(side), orientation);
        let light = brightness(cache, block, [x + dx, y + dy, z + dz]);
        builder.quad(quad, light, block::shade(side), WHITE);
    }
}

// ---- Levers -----------------------------------------------------

/// `renderBlockLever`: a cobblestone base and a handle tilted by the lever's state.
pub fn lever(input: &MeshInput, cache: &Cache, builder: &mut Builder, position: [i32; 3]) {
    let [x, y, z] = position;
    let metadata = cache.metadata(x, y, z);
    let orientation = metadata & 7;
    let on = metadata & 8 != 0;
    let (long, wide, deep) = (0.25, 3.0 / 16.0, 3.0 / 16.0);
    let (min, max) = match orientation {
        5 => (
            [0.5 - wide, 0.0, 0.5 - long],
            [0.5 + wide, deep, 0.5 + long],
        ),
        6 => (
            [0.5 - long, 0.0, 0.5 - wide],
            [0.5 + long, deep, 0.5 + wide],
        ),
        4 => (
            [0.5 - wide, 0.5 - long, 1.0 - deep],
            [0.5 + wide, 0.5 + long, 1.0],
        ),
        3 => (
            [0.5 - wide, 0.5 - long, 0.0],
            [0.5 + wide, 0.5 + long, deep],
        ),
        2 => (
            [1.0 - deep, 0.5 - long, 0.5 - wide],
            [1.0, 0.5 + long, 0.5 + wide],
        ),
        _ => (
            [0.0, 0.5 - long, 0.5 - wide],
            [deep, 0.5 + long, 0.5 + wide],
        ),
    };
    // The base is drawn like a cobblestone block of the lever's bounds
    let bounds = Bounds::new(min, max);
    block::standard_bounds(input, cache, builder, Block::COBBLESTONE, position, bounds);

    let rotate_x = |v: DVec3, angle: f32| {
        let (c, s) = (f64::from(math::cos(angle)), f64::from(math::sin(angle)));
        DVec3::new(v.x, v.y * c + v.z * s, v.z * c - v.y * s)
    };
    let rotate_y = |v: DVec3, angle: f32| {
        let (c, s) = (f64::from(math::cos(angle)), f64::from(math::sin(angle)));
        DVec3::new(v.x * c + v.z * s, v.y, v.z * c - v.x * s)
    };
    let (half, height) = (1.0 / 16.0, 10.0 / 16.0);
    let pi = std::f32::consts::PI;
    let tilt = pi * 2.0 / 9.0;
    // The bottom corners counterclockwise from (-x, -z), then the top ones
    let corners: [Vec3; 8] = std::array::from_fn(|i| {
        let x = if matches!(i % 4, 1 | 2) { half } else { -half };
        let z = if i % 4 >= 2 { half } else { -half };
        let y = if i >= 4 { height } else { 0.0 };
        let mut v = DVec3::new(
            f64::from(x as f32),
            f64::from(y as f32),
            f64::from(z as f32),
        );
        if on {
            v.z -= 1.0 / 16.0;
            v = rotate_x(v, tilt);
        } else {
            v.z += 1.0 / 16.0;
            v = rotate_x(v, -tilt);
        }
        if orientation == 6 {
            v = rotate_y(v, pi * 0.5);
        }
        if orientation < 5 {
            v.y -= 0.375;
            v = rotate_x(v, pi * 0.5);
            v = match orientation {
                3 => rotate_y(v, pi),
                2 => rotate_y(v, pi * 0.5),
                1 => rotate_y(v, pi * -0.5),
                _ => rotate_y(v, 0.0),
            };
            v + DVec3::new(0.5, 0.5, 0.5)
        } else {
            v + DVec3::new(0.5, f64::from(2.0_f32 / 16.0), 0.5)
        }
        .as_vec3()
    });
    let texture = Block::LEVER.properties().unwrap().texture;
    let (tx, ty) = (f32::from(texture & 15) * 16.0, f32::from(texture & 0xF0));
    let (u0, u1) = ((tx + 7.0) / 256.0, (tx + 9.0 - 0.01) / 256.0);
    let light = brightness(cache, Block::LEVER, position);
    for (face, indices) in [
        [0, 1, 2, 3],
        [7, 6, 5, 4],
        [1, 0, 4, 5],
        [2, 1, 5, 6],
        [3, 2, 6, 7],
        [0, 3, 7, 4],
    ]
    .into_iter()
    .enumerate()
    {
        let bottom = if face < 2 { 8.0 } else { 16.0 };
        let (v0, v1) = ((ty + 6.0) / 256.0, (ty + bottom - 0.01) / 256.0);
        let [a, b, c, d] = indices.map(|i| corners[i]);
        builder.quad(
            [(a, (u0, v1)), (b, (u1, v1)), (c, (u1, v0)), (d, (u0, v0))],
            light,
            Shade::Top,
            WHITE,
        );
    }
}

// ---- Repeaters --------------------------------------------------

/// `BlockRedstoneRepeater.repeaterTorchOffset`: how far the movable torch sits for each delay.
const REPEATER_TORCH_OFFSETS: [f32; 4] = [-0.0625, 1.0 / 16.0, 0.1875, 0.3125];

/// `renderBlockRepeater`: a slab with two torches and the arrow on top.
pub fn repeater(
    input: &MeshInput,
    cache: &Cache,
    builder: &mut Builder,
    block: Block,
    position: [i32; 3],
) {
    let [x, y, z] = position;
    let metadata = cache.metadata(x, y, z);
    let direction = metadata & 3;
    let delay = usize::from((metadata & 12) >> 2);
    let bounds = Bounds::with_height(2.0 / 16.0);
    block::standard_bounds(input, cache, builder, block, position, bounds);
    let light = brightness(cache, block, position);
    let shade = if block.light_emission() > 0 {
        Shade::Glow
    } else {
        Shade::Top
    };
    let offset = REPEATER_TORCH_OFFSETS[delay];
    let (first, second) = match direction {
        0 => ((0.0, offset), (0.0, -0.3125)),
        1 => ((-offset, 0.0), (0.3125, 0.0)),
        2 => ((0.0, -offset), (0.0, 0.3125)),
        _ => ((offset, 0.0), (-0.3125, 0.0)),
    };
    let torch = texture::face(block, metadata, 0).0;
    for (dx, dz) in [first, second] {
        let offset = Vec3::new(dx, -0.1875, dz);
        torch_at(builder, offset, Vec3::ZERO, torch, light, shade);
    }
    let [u0, u1, v0, v1] = tile(texture::face(block, metadata, 1).0);
    let (x0, x1, z0, z1) = (0.0, 1.0, 0.0, 1.0);
    let corners = match direction {
        2 => [(x1, z1), (x1, z0), (x0, z0), (x0, z1)],
        3 => [(x0, z1), (x1, z1), (x1, z0), (x0, z0)],
        1 => [(x1, z0), (x0, z0), (x0, z1), (x1, z1)],
        _ => [(x0, z0), (x0, z1), (x1, z1), (x1, z0)],
    };
    let top = 2.0 / 16.0;
    let uvs = [(u0, v0), (u0, v1), (u1, v1), (u1, v0)];
    let quad = std::array::from_fn(|i| (Vec3::new(corners[i].0, top, corners[i].1), uvs[i]));
    builder.quad(quad, light, shade, WHITE);
}

// ---- Redstone wire ----------------------------------------------

/// `renderBlockRedstoneWire`: dust tinted by its power, with an untinted overlay, running up
/// the sides of blocks it climbs.
pub fn wire(cache: &Cache, builder: &mut Builder, position: [i32; 3]) {
    let [x, y, z] = position;
    let power = cache.metadata(x, y, z);
    let level = f32::from(power) / 15.0;
    let red = if power == 0 { 0.3 } else { level * 0.6 + 0.4 };
    let green = (level * level * 0.7 - 0.5).max(0.0);
    let blue = (level * level * 0.6 - 0.7).max(0.0);
    let channel = |v: f32| (v * 255.0) as u32;
    let rgb = channel(red) << 16 | channel(green) << 8 | channel(blue);
    let light = brightness(cache, Block::REDSTONE_WIRE, position);
    let texture = Block::REDSTONE_WIRE.properties().unwrap().texture;
    let (tx, ty) = (f32::from(texture & 15) * 16.0, f32::from(texture & 0xF0));
    let range = |base: f32| (f64::from(base / 256.0), f64::from((base + 15.99) / 256.0));
    let ((mut u0, mut u1), (mut v0, mut v1)) = (range(tx), range(ty));
    let [west, east, north, south] = redstone::links(cache, x, y, z);
    let line = match (west || east, north || south) {
        (true, false) => 1,
        (false, true) => 2,
        _ => 0,
    };
    if line != 0 {
        ((u0, u1), (v0, v1)) = (range(tx + 16.0), range(ty));
    }
    let (mut x0, mut x1, mut z0, mut z1) = (0.0_f32, 1.0_f32, 0.0_f32, 1.0_f32);
    if line == 0 && (west || east || north || south) {
        let inset = 5.0 / 16.0;
        let shift = 1.25 / 64.0;
        if !west {
            x0 += inset;
            u0 += shift;
        }
        if !east {
            x1 -= inset;
            u1 -= shift;
        }
        if !north {
            z0 += inset;
            v0 += shift;
        }
        if !south {
            z1 -= inset;
            v1 -= shift;
        }
    }
    let height = 0.015625;
    let uvs = if line == 2 {
        [(u1, v1), (u0, v1), (u0, v0), (u1, v0)]
    } else {
        [(u1, v1), (u1, v0), (u0, v0), (u0, v1)]
    };
    let points = [(x1, z1), (x1, z0), (x0, z0), (x0, z1)].map(|(px, pz)| Vec3::new(px, height, pz));
    dust(builder, points, uvs, light, rgb);

    if cache.block(x, y + 1, z).is_normal_cube() {
        return;
    }
    let ((u0, u1), (v0, v1)) = (range(tx + 16.0), range(ty));
    let (top, edge) = (1.0 + 7.0 / 320.0, 0.015625);
    let climbs = |dx: i32, dz: i32| {
        cache.block(x + dx, y, z + dz).is_normal_cube()
            && cache.block(x + dx, y + 1, z + dz) == Block::REDSTONE_WIRE
    };
    let (down, up) = (
        [(u1, v0), (u0, v0), (u0, v1), (u1, v1)],
        [(u0, v1), (u1, v1), (u1, v0), (u0, v0)],
    );
    for (dx, dz) in [(-1, 0), (1, 0), (0, -1), (0, 1)] {
        if !climbs(dx, dz) {
            continue;
        }
        // West and south walls start at the top, east and north ones at the bottom
        let descends = dx < 0 || dz > 0;
        let wall = if dx + dz < 0 { edge } else { 1.0 - edge };
        let points = std::array::from_fn(|i| {
            let along = if i < 2 { 1.0 } else { 0.0 };
            let y = if descends == (i % 3 == 0) { top } else { 0.0 };
            if dx != 0 {
                Vec3::new(wall, y, along)
            } else {
                Vec3::new(along, y, wall)
            }
        });
        dust(
            builder,
            points,
            if descends { down } else { up },
            light,
            rgb,
        );
    }
}

/// One wire quad: the dust in its power's color, then its plain overlay a tile further down.
fn dust(builder: &mut Builder, points: [Vec3; 4], uvs: [(f64, f64); 4], light: Light, rgb: u32) {
    let quad =
        |dv: f64| std::array::from_fn(|i| (points[i], (uvs[i].0 as f32, (uvs[i].1 + dv) as f32)));
    builder.quad(quad(0.0), light, Shade::Top, rgb);
    builder.mark_cutout();
    builder.quad(quad(1.0 / 16.0), light, Shade::Top, WHITE);
}

// ---- Rails ------------------------------------------------------

/// `renderBlockMinecartTrack`: a two-sided plane, raised at one end on slopes.
pub fn rail(cache: &Cache, builder: &mut Builder, block: Block, position: [i32; 3]) {
    let [x, y, z] = position;
    let mut metadata = cache.metadata(x, y, z);
    let [u0, u1, v0, v1] = tile(texture::face(block, metadata, 0).0);
    if block != Block::RAIL {
        metadata &= 7;
    }
    let (x0, x1, z0, z1) = (0.0, 1.0, 0.0, 1.0);
    let corners = match metadata {
        1 | 2 | 3 | 7 => [(x1, z1), (x0, z1), (x0, z0), (x1, z0)],
        8 => [(x0, z1), (x0, z0), (x1, z0), (x1, z1)],
        9 => [(x0, z0), (x1, z0), (x1, z1), (x0, z1)],
        _ => [(x1, z0), (x1, z1), (x0, z1), (x0, z0)],
    };
    let (low, high) = (1.0 / 16.0, 1.0 / 16.0 + 1.0);
    let heights = match metadata {
        2 | 4 => [high, low, low, high],
        3 | 5 => [low, high, high, low],
        _ => [low; 4],
    };
    let uvs = [(u1, v0), (u1, v1), (u0, v1), (u0, v0)];
    let quad: [(Vec3, (f32, f32)); 4] =
        std::array::from_fn(|i| (Vec3::new(corners[i].0, heights[i], corners[i].1), uvs[i]));
    let light = brightness(cache, block, position);
    builder.quad(quad, light, Shade::Top, WHITE);
    let [a, b, c, d] = quad;
    builder.quad([d, c, b, a], light, Shade::Top, WHITE);
}

// ---- Pistons ----------------------------------------------------

/// The `uvRotate*` of `renderPistonBase` and `renderPistonExtension` for each facing, by face.
const PISTON_ROTATIONS: [[u8; 6]; 6] = [
    [0, 0, 3, 3, 3, 3],
    [0; 6],
    [0, 0, 0, 0, 2, 1],
    [3, 3, 0, 0, 1, 2],
    [1, 2, 1, 2, 0, 0],
    [2, 1, 2, 1, 0, 0],
];

fn facing(metadata: u8) -> usize {
    usize::from(metadata & 7)
}

/// The part of a block a quarter block thick towards (`towards`) or away from a facing.
fn plate(facing: usize, towards: bool) -> Bounds {
    let (mut min, mut max) = (Vec3::ZERO, Vec3::ONE);
    let axis = [1, 1, 2, 2, 0, 0][facing];
    let positive = facing % 2 == 1;
    match (positive, towards) {
        (false, true) => max[axis] = 0.25,
        (true, true) => min[axis] = 0.75,
        (false, false) => min[axis] = 0.25,
        (true, false) => max[axis] = 0.75,
    }
    Bounds { min, max }
}

/// `renderPistonBase`: a full block, or without the head's quarter while extended.
pub fn piston_base(
    input: &MeshInput,
    cache: &Cache,
    builder: &mut Builder,
    block: Block,
    position: [i32; 3],
) {
    let [x, y, z] = position;
    let metadata = cache.metadata(x, y, z);
    let facing = facing(metadata);
    if facing > 5 {
        return block::standard(input, cache, builder, block, position);
    }
    let bounds = if metadata & 8 != 0 {
        plate(facing, false)
    } else {
        Bounds::UNIT
    };
    block::standard_oriented(
        input,
        cache,
        builder,
        block,
        position,
        bounds,
        PISTON_ROTATIONS[facing],
    );
}

/// `renderPistonExtension`: the head plate and the four sides of its rod.
pub fn piston_extension(
    input: &MeshInput,
    cache: &Cache,
    builder: &mut Builder,
    position: [i32; 3],
    (length, shift): (f32, f32),
) {
    let [x, y, z] = position;
    let block = Block::PISTON_HEAD;
    let facing = facing(cache.metadata(x, y, z));
    if facing > 5 {
        return;
    }
    block::standard_oriented(
        input,
        cache,
        builder,
        block,
        position,
        plate(facing, true),
        PISTON_ROTATIONS[facing],
    );
    let light = brightness(cache, block, position);
    piston_rod(builder, facing, length, shift, light);
}

/// The rod quads of `renderPistonExtension`, `length` long and showing `shift` texels of the
/// rod texture.
fn piston_rod(builder: &mut Builder, facing: usize, length: f32, shift: f32, light: Light) {
    let (a, b) = (6.0 / 16.0, 10.0 / 16.0);
    let (start, end) = if facing.is_multiple_of(2) {
        (0.25, 0.25 + length)
    } else {
        (0.75 - length, 0.75)
    };
    use Shade::{Bottom, Top, XSide, ZSide};
    // Per rod side: x1, x2, y1, y2, z1, z2 (the rod's own axis spans start..end), and shading
    let rods: [([f32; 6], Shade); 4] = match facing {
        0 | 1 => [
            ([a, b, start, end, b, b], ZSide),
            ([b, a, start, end, a, a], ZSide),
            ([a, a, start, end, a, b], XSide),
            ([b, b, start, end, b, a], XSide),
        ],
        2 | 3 => [
            ([a, a, b, a, start, end], XSide),
            ([b, b, a, b, start, end], XSide),
            ([a, b, a, a, start, end], Bottom),
            ([b, a, b, b, start, end], Top),
        ],
        _ => [
            ([start, end, a, a, b, a], Bottom),
            ([start, end, b, b, a, b], Top),
            ([start, end, a, b, a, a], XSide),
            ([start, end, b, a, b, b], XSide),
        ],
    };
    let texture = 108_u8;
    let (tx, ty) = (f32::from(texture & 15) * 16.0, f32::from(texture & 0xF0));
    let (u0, v0) = (tx / 256.0, ty / 256.0);
    let u1 = ((f64::from(tx) + f64::from(shift) - 0.01) / 256.0) as f32;
    let v1 = ((f64::from(ty + 4.0) - 0.01) / 256.0) as f32;
    let p = Vec3::new;
    for ([x1, x2, y1, y2, z1, z2], shade) in rods {
        let points = match facing {
            0 | 1 => [p(x1, y2, z1), p(x1, y1, z1), p(x2, y1, z2), p(x2, y2, z2)],
            2 | 3 => [p(x1, y1, z2), p(x1, y1, z1), p(x2, y2, z1), p(x2, y2, z2)],
            _ => [p(x2, y1, z1), p(x1, y1, z1), p(x1, y2, z2), p(x2, y2, z2)],
        };
        let uvs = [(u1, v0), (u0, v0), (u0, v1), (u1, v1)];
        let quad = std::array::from_fn(|i| (points[i], uvs[i]));
        builder.quad(quad, light, shade, WHITE);
    }
}